yaver-feedback-react-native 0.9.8 → 0.9.10
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 +24 -10
- package/dist/DogfoodQuickControls.js +7 -0
- package/dist/DogfoodRuntime.d.ts +6 -1
- package/dist/DogfoodRuntime.js +9 -3
- package/dist/DogfoodSessionUi.d.ts +1 -0
- package/dist/DogfoodSessionUi.js +5 -2
- package/dist/FeedbackModal.js +249 -278
- package/dist/FeedbackModalTheme.d.ts +5 -0
- package/dist/FeedbackModalTheme.js +29 -0
- package/dist/P2PClient.d.ts +2 -0
- package/dist/P2PClient.js +1 -1
- package/dist/P2PDogfoodDriver.js +17 -0
- package/dist/YaverFeedback.js +10 -0
- package/dist/__tests__/DogfoodRuntime.test.js +14 -0
- package/dist/__tests__/FeedbackModalContract.test.d.ts +1 -0
- package/dist/__tests__/FeedbackModalContract.test.js +70 -0
- package/dist/__tests__/NativeDogfoodShortcut.test.js +23 -0
- package/dist/__tests__/P2PDogfoodDriver.test.js +4 -1
- package/dist/__tests__/YaverFeedback.test.js +37 -12
- package/dist/index.d.ts +4 -5
- package/dist/index.js +4 -5
- package/ios/YaverHotReload.swift +3 -4
- package/package.json +1 -1
- package/src/DogfoodQuickControls.tsx +7 -0
- package/src/DogfoodRuntime.ts +21 -4
- package/src/DogfoodSessionUi.tsx +6 -2
- package/src/FeedbackModal.tsx +319 -372
- package/src/FeedbackModalTheme.ts +29 -0
- package/src/P2PClient.ts +3 -1
- package/src/P2PDogfoodDriver.ts +17 -0
- package/src/YaverFeedback.ts +10 -0
- package/src/__tests__/DogfoodRuntime.test.ts +15 -0
- package/src/__tests__/FeedbackModalContract.test.ts +80 -0
- package/src/__tests__/NativeDogfoodShortcut.test.ts +25 -0
- package/src/__tests__/P2PDogfoodDriver.test.ts +4 -0
- package/src/__tests__/YaverFeedback.test.ts +40 -12
- package/src/index.ts +4 -5
package/src/FeedbackModal.tsx
CHANGED
|
@@ -2,8 +2,6 @@ import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
|
2
2
|
import {
|
|
3
3
|
ActivityIndicator,
|
|
4
4
|
DeviceEventEmitter,
|
|
5
|
-
Keyboard,
|
|
6
|
-
KeyboardAvoidingView,
|
|
7
5
|
Linking,
|
|
8
6
|
Modal,
|
|
9
7
|
Platform,
|
|
@@ -17,19 +15,6 @@ import {
|
|
|
17
15
|
} from 'react-native';
|
|
18
16
|
import { YaverFeedback } from './YaverFeedback';
|
|
19
17
|
import {
|
|
20
|
-
captureScreenshot,
|
|
21
|
-
// Launch scope for the feedback test SDK is intentionally smaller for now.
|
|
22
|
-
// Keep the dormant file-upload and screen-recording helpers nearby, but
|
|
23
|
-
// comment them out until we bring them back with stronger test coverage.
|
|
24
|
-
// pickFeedbackFile,
|
|
25
|
-
// startVideoRecording,
|
|
26
|
-
// stopVideoRecording,
|
|
27
|
-
} from './capture';
|
|
28
|
-
import { uploadFeedback } from './upload';
|
|
29
|
-
import { resolveReportIdentity } from './P2PClient';
|
|
30
|
-
import {
|
|
31
|
-
DeviceInfo,
|
|
32
|
-
FeedbackBundle,
|
|
33
18
|
OpenCodeConfigSummary,
|
|
34
19
|
OpenCodeProviderSummary,
|
|
35
20
|
RunnerAuthStatusRow,
|
|
@@ -39,7 +24,6 @@ import { QuickActionIcon } from './QuickActionIcon';
|
|
|
39
24
|
import { YaverModeBadge } from './YaverModeBadge';
|
|
40
25
|
import { VibeChatScreen } from './VibeChatScreen';
|
|
41
26
|
import { DogfoodQuickControls } from './DogfoodQuickControls';
|
|
42
|
-
import { DeployPanel } from './DeployPanel';
|
|
43
27
|
import { listReachableDevices, RemoteDevice } from './auth';
|
|
44
28
|
import { reloadActions } from './reloadActions';
|
|
45
29
|
import type { DevServerSnapshot, ReloadAction } from './reloadActions';
|
|
@@ -62,24 +46,21 @@ import {
|
|
|
62
46
|
} from './DogfoodRuntime';
|
|
63
47
|
import { createP2PDogfoodDriver } from './P2PDogfoodDriver';
|
|
64
48
|
import { DogfoodLanePicker, DogfoodLiveConsole, DogfoodStatusRail } from './DogfoodSessionUi';
|
|
49
|
+
import type { DogfoodRemoteRuntimeTarget } from './P2PClient';
|
|
50
|
+
import {
|
|
51
|
+
FEEDBACK_DOGFOOD_CONSOLE_COLORS,
|
|
52
|
+
FEEDBACK_DOGFOOD_LIGHT_COLORS,
|
|
53
|
+
} from './FeedbackModalTheme';
|
|
65
54
|
|
|
66
55
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* 2. Vibing — open a vibing session on the agent
|
|
71
|
-
* 3. Screenshot & Fix — capture the underlying app (modal hidden
|
|
72
|
-
* during capture), upload it, and trigger
|
|
73
|
-
* the fix loop
|
|
74
|
-
*
|
|
75
|
-
* The footer also has an explicit Cancel button so the icon tap path
|
|
76
|
-
* feels like a standard action sheet rather than a hidden modal.
|
|
56
|
+
* Feedback modal with one conversational control surface. Authenticated users
|
|
57
|
+
* land directly in Chat; screenshots, fixes, and deploy intent are expressed
|
|
58
|
+
* in that conversation and handled by the connected agent/MCP toolchain.
|
|
77
59
|
*/
|
|
78
60
|
|
|
79
61
|
type ActionState =
|
|
80
62
|
| 'idle'
|
|
81
63
|
| 'hot-reloading'
|
|
82
|
-
| 'capturing'
|
|
83
64
|
| 'vibing';
|
|
84
65
|
|
|
85
66
|
type MachineCardState = {
|
|
@@ -122,8 +103,20 @@ type DogfoodProjectChoice = {
|
|
|
122
103
|
framework?: string;
|
|
123
104
|
frameworks?: string[];
|
|
124
105
|
surfaces?: string[];
|
|
106
|
+
branch?: string;
|
|
107
|
+
gitRemote?: string;
|
|
125
108
|
};
|
|
126
109
|
|
|
110
|
+
type DogfoodSetupStage = 'setup' | 'lane' | 'runtime';
|
|
111
|
+
type DogfoodExpandedStep = 'runner' | 'checkout' | null;
|
|
112
|
+
|
|
113
|
+
function dogfoodCheckoutDetail(project: DogfoodProjectChoice | null): string {
|
|
114
|
+
if (!project) return 'Choose a Git checkout on the selected machine';
|
|
115
|
+
return [project.path, project.branch ? `branch ${project.branch}` : project.gitRemote ? 'Git checkout' : 'checkout']
|
|
116
|
+
.filter(Boolean)
|
|
117
|
+
.join(' · ');
|
|
118
|
+
}
|
|
119
|
+
|
|
127
120
|
const PRIMARY_RUNNER_IDS = ['claude', 'codex', 'opencode'] as const;
|
|
128
121
|
|
|
129
122
|
function normalizeRunnerStatusRows(rows: RunnerAuthStatusRow[]): RunnerCardState[] {
|
|
@@ -261,18 +254,13 @@ export const FeedbackModal: React.FC = () => {
|
|
|
261
254
|
// Which reload action is in flight, so only that button spins.
|
|
262
255
|
const [reloadingId, setReloadingId] = useState<string | null>(null);
|
|
263
256
|
const [runnerAuthModal, setRunnerAuthModal] = useState<string | null>(null);
|
|
264
|
-
//
|
|
265
|
-
//
|
|
266
|
-
// the user say WHAT they want to vibe on instead of firing a canned
|
|
267
|
-
// "pick something for me" prompt (which in 0.7.13 pointed Claude at
|
|
268
|
-
// the wrong project because the matcher grepped the prompt itself).
|
|
257
|
+
// Signed-in users get the composer immediately. Signed-out users still get
|
|
258
|
+
// an explicit entry action so auth/setup failures remain named and visible.
|
|
269
259
|
const [showVibeInput, setShowVibeInput] = useState(false);
|
|
270
|
-
const [showDeploy, setShowDeploy] = useState(false);
|
|
271
260
|
const [vibePrompt, setVibePrompt] = useState('');
|
|
272
261
|
const [lastVibeTaskId, setLastVibeTaskId] = useState<string | null>(null);
|
|
273
262
|
const [quickIconColorPreset, setQuickIconColorPreset] =
|
|
274
263
|
useState<QuickIconColorPreset | null>(null);
|
|
275
|
-
const [keyboardInset, setKeyboardInset] = useState(0);
|
|
276
264
|
const [machineCard, setMachineCard] = useState<MachineCardState>({
|
|
277
265
|
device: null,
|
|
278
266
|
reachable: null,
|
|
@@ -300,6 +288,11 @@ export const FeedbackModal: React.FC = () => {
|
|
|
300
288
|
const [dogfoodProject, setDogfoodProject] = useState<DogfoodProjectChoice | null>(null);
|
|
301
289
|
const [dogfoodLane, setDogfoodLane] = useState<DogfoodLane>('browser');
|
|
302
290
|
const [dogfoodNativeAvailable, setDogfoodNativeAvailable] = useState(false);
|
|
291
|
+
const [dogfoodBrowserAvailable, setDogfoodBrowserAvailable] = useState(false);
|
|
292
|
+
const [dogfoodNativeTargets, setDogfoodNativeTargets] = useState<DogfoodRemoteRuntimeTarget[]>([]);
|
|
293
|
+
const [dogfoodNativeTargetId, setDogfoodNativeTargetId] = useState('');
|
|
294
|
+
const [dogfoodSetupStage, setDogfoodSetupStage] = useState<DogfoodSetupStage>('setup');
|
|
295
|
+
const [dogfoodExpandedStep, setDogfoodExpandedStep] = useState<DogfoodExpandedStep>(null);
|
|
303
296
|
const [dogfoodRuntime, setDogfoodRuntime] = useState<DogfoodSnapshot | null>(null);
|
|
304
297
|
const [dogfoodSetupLoading, setDogfoodSetupLoading] = useState(false);
|
|
305
298
|
const dogfoodControllerRef = useRef<DogfoodController | null>(null);
|
|
@@ -330,12 +323,25 @@ export const FeedbackModal: React.FC = () => {
|
|
|
330
323
|
if (preferred) {
|
|
331
324
|
const framework = preferred.framework || onboarding.framework || 'expo';
|
|
332
325
|
const capabilities = await client.getDogfoodRemoteRuntimeCapabilities(preferred.path, framework).catch(() => null);
|
|
333
|
-
const
|
|
334
|
-
|
|
326
|
+
const targets = capabilities?.targets || [];
|
|
327
|
+
const nativeTargets = targets.filter((target) => target.id !== 'browser-window');
|
|
328
|
+
const enabledNativeTargets = nativeTargets.filter((target) => target.enabled);
|
|
329
|
+
const nativeRuntimeAvailable = enabledNativeTargets.length > 0;
|
|
330
|
+
const browserRuntimeAvailable = targets.some((target) => target.enabled && target.id === 'browser-window');
|
|
331
|
+
if (mountedRef.current) {
|
|
332
|
+
setDogfoodNativeAvailable(nativeRuntimeAvailable);
|
|
333
|
+
setDogfoodBrowserAvailable(browserRuntimeAvailable);
|
|
334
|
+
setDogfoodNativeTargets(nativeTargets);
|
|
335
|
+
setDogfoodNativeTargetId((current) => enabledNativeTargets.some((target) => target.id === current)
|
|
336
|
+
? current
|
|
337
|
+
: enabledNativeTargets[0]?.id || '');
|
|
338
|
+
}
|
|
335
339
|
const savedLane = await getPreferredDogfoodLane(onboarding.appId);
|
|
336
|
-
const savedSupported = dogfoodLaneOptions(framework, { nativeRuntimeAvailable })
|
|
340
|
+
const savedSupported = dogfoodLaneOptions(framework, { nativeRuntimeAvailable, browserRuntimeAvailable })
|
|
337
341
|
.some((option) => option.lane === savedLane && option.supported);
|
|
338
|
-
setDogfoodLane(savedLane && savedSupported
|
|
342
|
+
setDogfoodLane(savedLane && savedSupported
|
|
343
|
+
? savedLane
|
|
344
|
+
: defaultDogfoodLane(framework, { nativeRuntimeAvailable, browserRuntimeAvailable }));
|
|
339
345
|
}
|
|
340
346
|
} catch (cause) {
|
|
341
347
|
if (mountedRef.current) setDogfoodEnrollment({ status: 'failed', error: cause instanceof Error ? cause.message : String(cause) });
|
|
@@ -347,12 +353,31 @@ export const FeedbackModal: React.FC = () => {
|
|
|
347
353
|
useEffect(() => {
|
|
348
354
|
let cancelled = false;
|
|
349
355
|
setDogfoodNativeAvailable(false);
|
|
356
|
+
setDogfoodBrowserAvailable(false);
|
|
357
|
+
setDogfoodNativeTargets([]);
|
|
358
|
+
setDogfoodNativeTargetId('');
|
|
350
359
|
const client = YaverFeedback.getP2PClient();
|
|
351
360
|
if (!client || !dogfoodProject) return () => { cancelled = true; };
|
|
352
361
|
const framework = dogfoodProject.framework || YaverFeedback.getDogfoodOnboarding()?.framework || 'expo';
|
|
353
362
|
void client.getDogfoodRemoteRuntimeCapabilities(dogfoodProject.path, framework)
|
|
354
363
|
.then((value) => {
|
|
355
|
-
if (
|
|
364
|
+
if (cancelled) return;
|
|
365
|
+
const nativeTargets = value.targets.filter((target) => target.id !== 'browser-window');
|
|
366
|
+
const enabledNativeTargets = nativeTargets.filter((target) => target.enabled);
|
|
367
|
+
setDogfoodNativeAvailable(enabledNativeTargets.length > 0);
|
|
368
|
+
setDogfoodBrowserAvailable(value.targets.some((target) => target.enabled && target.id === 'browser-window'));
|
|
369
|
+
setDogfoodNativeTargets(nativeTargets);
|
|
370
|
+
setDogfoodNativeTargetId((current) => enabledNativeTargets.some((target) => target.id === current)
|
|
371
|
+
? current
|
|
372
|
+
: enabledNativeTargets[0]?.id || '');
|
|
373
|
+
const laneCapabilities = {
|
|
374
|
+
nativeRuntimeAvailable: enabledNativeTargets.length > 0,
|
|
375
|
+
browserRuntimeAvailable: value.targets.some((target) => target.enabled && target.id === 'browser-window'),
|
|
376
|
+
};
|
|
377
|
+
setDogfoodLane((current) => dogfoodLaneOptions(framework, laneCapabilities)
|
|
378
|
+
.some((option) => option.lane === current && option.supported)
|
|
379
|
+
? current
|
|
380
|
+
: defaultDogfoodLane(framework, laneCapabilities));
|
|
356
381
|
})
|
|
357
382
|
.catch(() => {});
|
|
358
383
|
return () => { cancelled = true; };
|
|
@@ -369,13 +394,15 @@ export const FeedbackModal: React.FC = () => {
|
|
|
369
394
|
workDir: dogfoodProject.path,
|
|
370
395
|
framework,
|
|
371
396
|
lane: dogfoodLane,
|
|
397
|
+
nativeTargetId: dogfoodLane === 'webrtc' ? dogfoodNativeTargetId : undefined,
|
|
372
398
|
}, createP2PDogfoodDriver(client), {
|
|
373
399
|
onChange: (snapshot) => { if (mountedRef.current) setDogfoodRuntime(snapshot); },
|
|
374
400
|
});
|
|
375
401
|
dogfoodControllerRef.current = controller;
|
|
376
402
|
setDogfoodRuntime(controller.snapshot());
|
|
403
|
+
setDogfoodSetupStage('runtime');
|
|
377
404
|
await controller.trigger().catch(() => {});
|
|
378
|
-
}, [dogfoodLane, dogfoodProject]);
|
|
405
|
+
}, [dogfoodLane, dogfoodNativeTargetId, dogfoodProject]);
|
|
379
406
|
|
|
380
407
|
/**
|
|
381
408
|
* Ask the machine what its dev server is doing, so the reload actions can
|
|
@@ -571,16 +598,20 @@ export const FeedbackModal: React.FC = () => {
|
|
|
571
598
|
// user's feedback preference merely to open Developer Mode.
|
|
572
599
|
if (YaverFeedback.isEnabled() || onboarding) {
|
|
573
600
|
const directDogfood = YaverFeedback.getDogfoodStatus().active;
|
|
601
|
+
const authenticated = YaverFeedback.isAuthed();
|
|
574
602
|
setDogfoodActive(directDogfood);
|
|
575
603
|
setVisible(true);
|
|
576
604
|
setError(null);
|
|
577
605
|
setToast(null);
|
|
578
606
|
setProgress(null);
|
|
579
607
|
setAction('idle');
|
|
580
|
-
setShowVibeInput(directDogfood);
|
|
608
|
+
setShowVibeInput(authenticated || directDogfood);
|
|
581
609
|
setVibePrompt('');
|
|
582
610
|
if (onboarding) {
|
|
583
|
-
setActiveTab('settings');
|
|
611
|
+
setActiveTab(authenticated ? 'chat' : 'settings');
|
|
612
|
+
setDogfoodSetupStage('setup');
|
|
613
|
+
setDogfoodExpandedStep(null);
|
|
614
|
+
setDogfoodRuntime(null);
|
|
584
615
|
void loadDogfoodOnboarding();
|
|
585
616
|
}
|
|
586
617
|
// Re-read the "user hid the quick icon" flag on every open so
|
|
@@ -652,31 +683,13 @@ export const FeedbackModal: React.FC = () => {
|
|
|
652
683
|
return () => clearInterval(interval);
|
|
653
684
|
}, [loadRunnerStatuses, loadSelectedMachine, refreshDevSnapshot, visible]);
|
|
654
685
|
|
|
655
|
-
useEffect(() => {
|
|
656
|
-
if (!visible) {
|
|
657
|
-
setKeyboardInset(0);
|
|
658
|
-
return;
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
|
|
662
|
-
const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
|
|
663
|
-
const showSub = Keyboard.addListener(showEvent, (event) => {
|
|
664
|
-
setKeyboardInset(event.endCoordinates?.height ?? 0);
|
|
665
|
-
});
|
|
666
|
-
const hideSub = Keyboard.addListener(hideEvent, () => {
|
|
667
|
-
setKeyboardInset(0);
|
|
668
|
-
});
|
|
669
|
-
return () => {
|
|
670
|
-
showSub.remove();
|
|
671
|
-
hideSub.remove();
|
|
672
|
-
};
|
|
673
|
-
}, [visible]);
|
|
674
|
-
|
|
675
686
|
useEffect(() => {
|
|
676
687
|
if (!visible || !showVibeInput) return;
|
|
677
|
-
|
|
688
|
+
// UIKit owns keyboard insets on the ScrollView. This one bounded scroll only
|
|
689
|
+
// reveals the newly mounted composer; it does not add a second inset.
|
|
690
|
+
const timer = setTimeout(() => scrollRef.current?.scrollToEnd({ animated: true }), 80);
|
|
678
691
|
return () => clearTimeout(timer);
|
|
679
|
-
}, [
|
|
692
|
+
}, [showVibeInput, visible]);
|
|
680
693
|
|
|
681
694
|
const closeSoon = useCallback((delayMs = 1200) => {
|
|
682
695
|
setTimeout(() => {
|
|
@@ -693,6 +706,9 @@ export const FeedbackModal: React.FC = () => {
|
|
|
693
706
|
setShowVibeInput(false);
|
|
694
707
|
setVibePrompt('');
|
|
695
708
|
setRunnerStatusError(null);
|
|
709
|
+
setDogfoodSetupStage('setup');
|
|
710
|
+
setDogfoodExpandedStep(null);
|
|
711
|
+
setDogfoodRuntime(null);
|
|
696
712
|
void dogfoodControllerRef.current?.stop().catch(() => {});
|
|
697
713
|
dogfoodControllerRef.current = null;
|
|
698
714
|
YaverFeedback.clearDogfoodOnboarding();
|
|
@@ -886,123 +902,7 @@ export const FeedbackModal: React.FC = () => {
|
|
|
886
902
|
}
|
|
887
903
|
}, [closeSoon, loadSelectedMachine, runWithReconnect]);
|
|
888
904
|
|
|
889
|
-
|
|
890
|
-
bundle: FeedbackBundle,
|
|
891
|
-
fixOnUpload: boolean,
|
|
892
|
-
successToast: string,
|
|
893
|
-
failureToast?: string,
|
|
894
|
-
) => {
|
|
895
|
-
const client = YaverFeedback.getP2PClient();
|
|
896
|
-
const config = YaverFeedback.getConfig();
|
|
897
|
-
if (!client || !config?.agentUrl) {
|
|
898
|
-
setError('Not connected to the agent yet.');
|
|
899
|
-
return;
|
|
900
|
-
}
|
|
901
|
-
try {
|
|
902
|
-
const uploaded = await uploadFeedback(
|
|
903
|
-
config.agentUrl,
|
|
904
|
-
config.authToken ?? '',
|
|
905
|
-
bundle,
|
|
906
|
-
YaverFeedback.getRelayPassword(),
|
|
907
|
-
);
|
|
908
|
-
// The agent returns the new report id as `id` (see
|
|
909
|
-
// feedback_http.go::ReceiveFeedback). Trigger the fix loop if we got
|
|
910
|
-
// one back; otherwise just ack the upload.
|
|
911
|
-
const reportId =
|
|
912
|
-
(uploaded as { id?: string; reportId?: string } | null | undefined)?.id ??
|
|
913
|
-
(uploaded as { reportId?: string } | null | undefined)?.reportId;
|
|
914
|
-
if (reportId && fixOnUpload) {
|
|
915
|
-
try {
|
|
916
|
-
await client.triggerFix(reportId);
|
|
917
|
-
setToast(successToast);
|
|
918
|
-
} catch (err: unknown) {
|
|
919
|
-
setToast(failureToast ?? 'Report uploaded — fix trigger failed');
|
|
920
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
921
|
-
}
|
|
922
|
-
} else {
|
|
923
|
-
setToast(successToast);
|
|
924
|
-
}
|
|
925
|
-
closeSoon(1400);
|
|
926
|
-
} catch (err: unknown) {
|
|
927
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
928
|
-
}
|
|
929
|
-
}, [closeSoon]);
|
|
930
|
-
|
|
931
|
-
const handleScreenshotAndFix = useCallback(async () => {
|
|
932
|
-
setAction('capturing');
|
|
933
|
-
setError(null);
|
|
934
|
-
|
|
935
|
-
setVisible(false);
|
|
936
|
-
await new Promise((resolve) => setTimeout(resolve, 350));
|
|
937
|
-
|
|
938
|
-
let path: string;
|
|
939
|
-
try {
|
|
940
|
-
path = await captureScreenshot();
|
|
941
|
-
} catch (err: unknown) {
|
|
942
|
-
setVisible(true);
|
|
943
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
944
|
-
setAction('idle');
|
|
945
|
-
return;
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
setVisible(true);
|
|
949
|
-
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
950
|
-
|
|
951
|
-
try {
|
|
952
|
-
const { Dimensions } = require('react-native');
|
|
953
|
-
const { width, height } = Dimensions.get('window');
|
|
954
|
-
const cfg = YaverFeedback.getConfig();
|
|
955
|
-
const identity = resolveReportIdentity({
|
|
956
|
-
projectName: cfg?.projectName,
|
|
957
|
-
bundleId: cfg?.bundleId,
|
|
958
|
-
surface: cfg?.surface,
|
|
959
|
-
surfaces: cfg?.surfaces,
|
|
960
|
-
stack: cfg?.stack,
|
|
961
|
-
stacks: cfg?.stacks,
|
|
962
|
-
testSurfaces: cfg?.testSurfaces,
|
|
963
|
-
feedbackSdk: cfg?.feedbackSdk,
|
|
964
|
-
feedbackTransport: cfg?.feedbackTransport,
|
|
965
|
-
voiceCapabilities: cfg?.voiceCapabilities,
|
|
966
|
-
sttProvider: cfg?.sttProvider,
|
|
967
|
-
ttsProvider: cfg?.ttsProvider,
|
|
968
|
-
});
|
|
969
|
-
const deviceInfo: DeviceInfo = {
|
|
970
|
-
platform: Platform.OS,
|
|
971
|
-
osVersion: String(Platform.Version),
|
|
972
|
-
model: Platform.OS === 'ios' ? 'iOS Device' : 'Android Device',
|
|
973
|
-
screenWidth: width,
|
|
974
|
-
screenHeight: height,
|
|
975
|
-
appName: identity.appName,
|
|
976
|
-
};
|
|
977
|
-
const capturedErrors = YaverFeedback.getCapturedErrors();
|
|
978
|
-
const bundle: FeedbackBundle = {
|
|
979
|
-
metadata: {
|
|
980
|
-
timestamp: new Date().toISOString(),
|
|
981
|
-
deviceInfo,
|
|
982
|
-
app: identity.app,
|
|
983
|
-
project: identity.project,
|
|
984
|
-
userNote: '[Screenshot + Fix]',
|
|
985
|
-
},
|
|
986
|
-
screenshots: [path],
|
|
987
|
-
errors: capturedErrors.length > 0 ? capturedErrors : undefined,
|
|
988
|
-
};
|
|
989
|
-
await uploadBundleWithOptionalFix(
|
|
990
|
-
bundle,
|
|
991
|
-
true,
|
|
992
|
-
'Fix task started',
|
|
993
|
-
);
|
|
994
|
-
} finally {
|
|
995
|
-
if (mountedRef.current) setAction('idle');
|
|
996
|
-
}
|
|
997
|
-
}, [uploadBundleWithOptionalFix]);
|
|
998
|
-
|
|
999
|
-
/*
|
|
1000
|
-
const handleFileUpload = useCallback(async () => {
|
|
1001
|
-
...
|
|
1002
|
-
}, [uploadBundleWithOptionalFix]);
|
|
1003
|
-
*/
|
|
1004
|
-
|
|
1005
|
-
// ─── 3. Vibing ─────────────────────────────────────────────────────
|
|
905
|
+
// ─── Chat ──────────────────────────────────────────────────────────
|
|
1006
906
|
// First tap expands the input; second submit fires the actual
|
|
1007
907
|
// /vibing/execute. Mirrors the Yaver mobile app's Vibing tab —
|
|
1008
908
|
// user types what they want, hits Send, sees the task id back. If
|
|
@@ -1151,45 +1051,54 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1151
1051
|
&& (selectedDogfoodRunner.ready || selectedDogfoodRunner.authConfigured);
|
|
1152
1052
|
const dogfoodModelReady = !selectedDogfoodRunner?.models?.length
|
|
1153
1053
|
|| !!preferredModel && selectedDogfoodRunner.models.some((model) => model.id === preferredModel);
|
|
1154
|
-
const
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
{
|
|
1166
|
-
key: 'machine', label: 'Remote PC',
|
|
1167
|
-
detail: machineCard.device ? machineCard.title : 'Choose a reachable development machine',
|
|
1168
|
-
tone: machineCard.device && machineCard.status === 'live' ? 'ready' as const : 'attention' as const,
|
|
1169
|
-
},
|
|
1170
|
-
{
|
|
1171
|
-
key: 'installation', label: 'This installation',
|
|
1172
|
-
detail: dogfoodEnrollment?.status === 'active' ? 'Device key approved' : dogfoodEnrollment?.status || 'Checking device key',
|
|
1173
|
-
tone: dogfoodEnrollment?.status === 'active' ? 'ready' as const
|
|
1174
|
-
: dogfoodEnrollment?.status === 'failed' ? 'blocked' as const : 'pending' as const,
|
|
1175
|
-
},
|
|
1054
|
+
const dogfoodFramework = dogfoodProject?.framework || YaverFeedback.getDogfoodOnboarding()?.framework || 'expo';
|
|
1055
|
+
const dogfoodLaneChoices = dogfoodLaneOptions(dogfoodFramework, {
|
|
1056
|
+
nativeRuntimeAvailable: dogfoodNativeAvailable,
|
|
1057
|
+
browserRuntimeAvailable: dogfoodBrowserAvailable,
|
|
1058
|
+
});
|
|
1059
|
+
const selectedDogfoodNativeTarget = dogfoodNativeTargets.find((target) => target.id === dogfoodNativeTargetId) || null;
|
|
1060
|
+
const dogfoodLaneReady = dogfoodLaneChoices.some((option) => option.lane === dogfoodLane && option.supported)
|
|
1061
|
+
&& (dogfoodLane !== 'webrtc' || !!selectedDogfoodNativeTarget?.enabled);
|
|
1062
|
+
const dogfoodMachineReady = !!machineCard.device && machineCard.status === 'live';
|
|
1063
|
+
const dogfoodSetupReady = dogfoodMachineReady && !!dogfoodProject && dogfoodRunnerReady && dogfoodModelReady;
|
|
1064
|
+
const dogfoodSetupSteps = [
|
|
1176
1065
|
{
|
|
1177
|
-
key: '
|
|
1178
|
-
|
|
1179
|
-
|
|
1066
|
+
key: 'box',
|
|
1067
|
+
label: 'Remote box',
|
|
1068
|
+
detail: dogfoodMachineReady ? machineCard.title : 'Choose a reachable development machine',
|
|
1069
|
+
tone: dogfoodMachineReady ? 'ready' as const : 'attention' as const,
|
|
1070
|
+
actionLabel: machineCard.device ? 'Change' : 'Pick',
|
|
1071
|
+
onAction: () => YaverFeedback.showMachinePicker(),
|
|
1180
1072
|
},
|
|
1181
1073
|
{
|
|
1182
|
-
key: '
|
|
1183
|
-
|
|
1184
|
-
|
|
1074
|
+
key: 'runner',
|
|
1075
|
+
label: 'Runner',
|
|
1076
|
+
detail: dogfoodRunnerReady
|
|
1077
|
+
? [selectedDogfoodRunner?.name || preferredRunner || 'Ready', preferredModel || 'default model'].join(' · ')
|
|
1078
|
+
: 'Choose or configure a coding runner',
|
|
1079
|
+
tone: dogfoodRunnerReady && dogfoodModelReady ? 'ready' as const : 'attention' as const,
|
|
1080
|
+
actionLabel: dogfoodExpandedStep === 'runner' ? 'Done' : dogfoodRunnerReady ? 'Change' : 'Choose',
|
|
1081
|
+
expanded: dogfoodExpandedStep === 'runner',
|
|
1082
|
+
onAction: () => setDogfoodExpandedStep((current) => current === 'runner' ? null : 'runner'),
|
|
1185
1083
|
},
|
|
1186
1084
|
{
|
|
1187
|
-
key: '
|
|
1188
|
-
|
|
1189
|
-
|
|
1085
|
+
key: 'checkout',
|
|
1086
|
+
label: 'Checkout',
|
|
1087
|
+
detail: dogfoodCheckoutDetail(dogfoodProject),
|
|
1088
|
+
tone: dogfoodProject ? 'ready' as const : 'attention' as const,
|
|
1089
|
+
actionLabel: dogfoodExpandedStep === 'checkout' ? 'Done' : dogfoodProject ? 'Change' : 'Choose',
|
|
1090
|
+
expanded: dogfoodExpandedStep === 'checkout',
|
|
1091
|
+
onAction: () => setDogfoodExpandedStep((current) => current === 'checkout' ? null : 'checkout'),
|
|
1190
1092
|
},
|
|
1191
1093
|
];
|
|
1192
|
-
const dogfoodStartBlocked = !
|
|
1094
|
+
const dogfoodStartBlocked = !dogfoodSetupReady || !dogfoodLaneReady;
|
|
1095
|
+
const dogfoodSourceLabel = dogfoodLane === 'webrtc'
|
|
1096
|
+
? selectedDogfoodNativeTarget
|
|
1097
|
+
? [selectedDogfoodNativeTarget.label, selectedDogfoodNativeTarget.platform].filter(Boolean).join(' · ')
|
|
1098
|
+
: 'Native simulator, emulator, or device'
|
|
1099
|
+
: dogfoodLane === 'hermes'
|
|
1100
|
+
? `Hermes build · ${machineCard.title}`
|
|
1101
|
+
: `${dogfoodFramework === 'flutter' ? 'Flutter web compiler' : 'Metro / browser build'} · ${machineCard.title}`;
|
|
1193
1102
|
|
|
1194
1103
|
// Once the user fires off a vibe task, swap the entire modal body
|
|
1195
1104
|
// for the live chat screen. The chat manages its own SSE
|
|
@@ -1252,14 +1161,9 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1252
1161
|
transparent
|
|
1253
1162
|
onRequestClose={handleClose}
|
|
1254
1163
|
>
|
|
1255
|
-
<
|
|
1256
|
-
<
|
|
1257
|
-
|
|
1258
|
-
keyboardVerticalOffset={Platform.OS === 'ios' ? 12 : 0}
|
|
1259
|
-
style={styles.kbAvoider}
|
|
1260
|
-
pointerEvents="box-none"
|
|
1261
|
-
>
|
|
1262
|
-
<Pressable
|
|
1164
|
+
<View style={styles.overlay}>
|
|
1165
|
+
<Pressable style={styles.backdrop} onPress={handleClose} accessibilityLabel="Close feedback" />
|
|
1166
|
+
<View
|
|
1263
1167
|
// Tablet: cap modal width and center as a card-style
|
|
1264
1168
|
// sheet rather than a phone bottom sheet that stretches
|
|
1265
1169
|
// across a 12.9" iPad. Phone behaviour unchanged.
|
|
@@ -1275,20 +1179,11 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1275
1179
|
}
|
|
1276
1180
|
: null,
|
|
1277
1181
|
]}
|
|
1278
|
-
onPress={(e) => {
|
|
1279
|
-
e.stopPropagation();
|
|
1280
|
-
Keyboard.dismiss();
|
|
1281
|
-
}}
|
|
1282
1182
|
>
|
|
1283
1183
|
<ScrollView
|
|
1284
1184
|
ref={scrollRef}
|
|
1285
1185
|
style={styles.scroll}
|
|
1286
|
-
contentContainerStyle={
|
|
1287
|
-
styles.scrollContent,
|
|
1288
|
-
showVibeInput && keyboardInset > 0
|
|
1289
|
-
? { paddingBottom: 8 + keyboardInset }
|
|
1290
|
-
: null,
|
|
1291
|
-
]}
|
|
1186
|
+
contentContainerStyle={styles.scrollContent}
|
|
1292
1187
|
keyboardShouldPersistTaps="handled"
|
|
1293
1188
|
keyboardDismissMode={Platform.OS === 'ios' ? 'interactive' : 'on-drag'}
|
|
1294
1189
|
contentInsetAdjustmentBehavior="always"
|
|
@@ -1329,10 +1224,17 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1329
1224
|
<View style={styles.dogfoodWizard}>
|
|
1330
1225
|
<Text style={styles.dogfoodWizardTitle}>Dogfood this app</Text>
|
|
1331
1226
|
<Text style={styles.dogfoodWizardHint}>
|
|
1332
|
-
|
|
1227
|
+
{dogfoodEnrollment?.status === 'active'
|
|
1228
|
+
? 'Signed in · this installation is approved'
|
|
1229
|
+
: `Signed in · installation ${dogfoodEnrollment?.status || 'checking'}`}
|
|
1333
1230
|
</Text>
|
|
1334
|
-
|
|
1335
|
-
|
|
1231
|
+
{dogfoodEnrollment?.status === 'active' && dogfoodSetupStage === 'setup' ? (
|
|
1232
|
+
<DogfoodStatusRail
|
|
1233
|
+
steps={dogfoodSetupSteps}
|
|
1234
|
+
colors={FEEDBACK_DOGFOOD_LIGHT_COLORS}
|
|
1235
|
+
/>
|
|
1236
|
+
) : null}
|
|
1237
|
+
{dogfoodEnrollment?.status !== 'active' && dogfoodEnrollment?.installationId ? (
|
|
1336
1238
|
<Text selectable style={styles.dogfoodInstallationId}>
|
|
1337
1239
|
This device · {dogfoodEnrollment.installationId}
|
|
1338
1240
|
</Text>
|
|
@@ -1352,96 +1254,165 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1352
1254
|
</View>
|
|
1353
1255
|
) : (
|
|
1354
1256
|
<>
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1257
|
+
{dogfoodSetupStage === 'setup' ? (
|
|
1258
|
+
<>
|
|
1259
|
+
{dogfoodExpandedStep === 'runner' ? (
|
|
1260
|
+
<View style={styles.dogfoodExpandedPanel}>
|
|
1261
|
+
<Text style={styles.dogfoodStepLabel}>Coding runner</Text>
|
|
1262
|
+
<View style={styles.dogfoodChoiceRow}>
|
|
1263
|
+
{runnerCards.filter((row) => row.ready || row.authConfigured).map((row) => (
|
|
1264
|
+
<Pressable
|
|
1265
|
+
key={row.id}
|
|
1266
|
+
onPress={() => {
|
|
1267
|
+
const nextModel = row.models?.find((model) => model.isDefault)?.id || row.models?.[0]?.id || '';
|
|
1268
|
+
setPreferredRunnerState(row.id);
|
|
1269
|
+
setPreferredModelState(nextModel);
|
|
1270
|
+
void setPreferredRunner(row.id);
|
|
1271
|
+
void setPreferredModel(nextModel || null);
|
|
1272
|
+
}}
|
|
1273
|
+
style={[styles.dogfoodChoice, preferredRunner === row.id && styles.dogfoodChoiceSelected]}
|
|
1274
|
+
accessibilityRole="button"
|
|
1275
|
+
accessibilityState={{ selected: preferredRunner === row.id }}
|
|
1276
|
+
>
|
|
1277
|
+
<Text style={[styles.dogfoodChoiceText, preferredRunner === row.id && styles.dogfoodChoiceTextSelected]}>{row.name}</Text>
|
|
1278
|
+
</Pressable>
|
|
1279
|
+
))}
|
|
1280
|
+
</View>
|
|
1281
|
+
{readyRunnerCount === 0 ? (
|
|
1282
|
+
<Text style={styles.dogfoodWizardHint}>Configure a coding runner below, then retry.</Text>
|
|
1283
|
+
) : null}
|
|
1284
|
+
{selectedDogfoodRunner?.models?.length ? (
|
|
1285
|
+
<>
|
|
1286
|
+
<Text style={styles.dogfoodStepLabel}>Model</Text>
|
|
1287
|
+
<View style={styles.dogfoodChoiceRow}>
|
|
1288
|
+
{selectedDogfoodRunner.models.map((model) => (
|
|
1289
|
+
<Pressable
|
|
1290
|
+
key={model.id}
|
|
1291
|
+
onPress={() => {
|
|
1292
|
+
setPreferredModelState(model.id);
|
|
1293
|
+
void setPreferredModel(model.id);
|
|
1294
|
+
}}
|
|
1295
|
+
style={[styles.dogfoodChoice, preferredModel === model.id && styles.dogfoodChoiceSelected]}
|
|
1296
|
+
>
|
|
1297
|
+
<Text style={[styles.dogfoodChoiceText, preferredModel === model.id && styles.dogfoodChoiceTextSelected]}>{model.name || model.id}</Text>
|
|
1298
|
+
</Pressable>
|
|
1299
|
+
))}
|
|
1300
|
+
</View>
|
|
1301
|
+
</>
|
|
1302
|
+
) : null}
|
|
1303
|
+
</View>
|
|
1304
|
+
) : null}
|
|
1305
|
+
{dogfoodExpandedStep === 'checkout' ? (
|
|
1306
|
+
<View style={styles.dogfoodExpandedPanel}>
|
|
1307
|
+
<Text style={styles.dogfoodStepLabel}>Git checkout on remote box</Text>
|
|
1308
|
+
{dogfoodProjects.map((project) => (
|
|
1309
|
+
<Pressable
|
|
1310
|
+
key={project.path}
|
|
1311
|
+
onPress={() => {
|
|
1312
|
+
setDogfoodProject(project);
|
|
1313
|
+
setDogfoodLane(defaultDogfoodLane(project.framework || YaverFeedback.getDogfoodOnboarding()?.framework || 'expo'));
|
|
1314
|
+
setDogfoodRuntime(null);
|
|
1315
|
+
}}
|
|
1316
|
+
style={[styles.dogfoodProjectChoice, dogfoodProject?.path === project.path && styles.dogfoodChoiceSelected]}
|
|
1317
|
+
>
|
|
1318
|
+
<Text style={[styles.dogfoodChoiceText, dogfoodProject?.path === project.path && styles.dogfoodChoiceTextSelected]}>{project.name}</Text>
|
|
1319
|
+
<Text style={styles.dogfoodProjectDetail}>{dogfoodCheckoutDetail(project)}</Text>
|
|
1320
|
+
</Pressable>
|
|
1321
|
+
))}
|
|
1322
|
+
</View>
|
|
1323
|
+
) : null}
|
|
1324
|
+
<ActionRow
|
|
1325
|
+
label="Choose runtime"
|
|
1326
|
+
tint="#5645d8"
|
|
1376
1327
|
onPress={() => {
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
setPreferredModelState(nextModel);
|
|
1380
|
-
void setPreferredRunner(row.id);
|
|
1381
|
-
void setPreferredModel(nextModel || null);
|
|
1328
|
+
setDogfoodExpandedStep(null);
|
|
1329
|
+
setDogfoodSetupStage('lane');
|
|
1382
1330
|
}}
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
accessibilityLabel={`Use ${row.name} for Dogfood`}
|
|
1387
|
-
>
|
|
1388
|
-
<Text style={[styles.dogfoodChoiceText, preferredRunner === row.id && styles.dogfoodChoiceTextSelected]}>{row.name}</Text>
|
|
1389
|
-
</Pressable>
|
|
1390
|
-
))}
|
|
1391
|
-
</View>
|
|
1392
|
-
{readyRunnerCount === 0 ? (
|
|
1393
|
-
<Text style={styles.dogfoodWizardHint}>Sign in or configure a coding agent under Coding Agents below.</Text>
|
|
1331
|
+
disabled={!dogfoodSetupReady}
|
|
1332
|
+
/>
|
|
1333
|
+
</>
|
|
1394
1334
|
) : null}
|
|
1395
|
-
|
|
1335
|
+
|
|
1336
|
+
{dogfoodSetupStage === 'lane' ? (
|
|
1396
1337
|
<>
|
|
1397
|
-
<
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
<
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1338
|
+
<View style={styles.dogfoodStageHeader}>
|
|
1339
|
+
<View style={styles.dogfoodStageCopy}>
|
|
1340
|
+
<Text style={styles.dogfoodStepLabel}>Runtime</Text>
|
|
1341
|
+
<Text style={styles.dogfoodStageTitle}>{dogfoodProject?.name} · {dogfoodFramework}</Text>
|
|
1342
|
+
</View>
|
|
1343
|
+
<Pressable onPress={() => setDogfoodSetupStage('setup')} style={styles.dogfoodSmallAction}>
|
|
1344
|
+
<Text style={styles.dogfoodSmallActionText}>Back</Text>
|
|
1345
|
+
</Pressable>
|
|
1346
|
+
</View>
|
|
1347
|
+
<DogfoodLanePicker
|
|
1348
|
+
options={dogfoodLaneChoices}
|
|
1349
|
+
selected={dogfoodLane}
|
|
1350
|
+
colors={FEEDBACK_DOGFOOD_LIGHT_COLORS}
|
|
1351
|
+
onSelect={(lane) => {
|
|
1352
|
+
setDogfoodLane(lane);
|
|
1353
|
+
const appId = YaverFeedback.getDogfoodOnboarding()?.appId;
|
|
1354
|
+
if (appId) void setPreferredDogfoodLane(appId, lane);
|
|
1355
|
+
}}
|
|
1356
|
+
/>
|
|
1357
|
+
{dogfoodLane === 'webrtc' ? (
|
|
1358
|
+
<View style={styles.dogfoodExpandedPanel}>
|
|
1359
|
+
<Text style={styles.dogfoodStepLabel}>Simulator, emulator, or device</Text>
|
|
1360
|
+
{dogfoodNativeTargets.map((target) => (
|
|
1361
|
+
<Pressable
|
|
1362
|
+
key={target.id}
|
|
1363
|
+
disabled={!target.enabled}
|
|
1364
|
+
onPress={() => setDogfoodNativeTargetId(target.id)}
|
|
1365
|
+
style={[
|
|
1366
|
+
styles.dogfoodProjectChoice,
|
|
1367
|
+
dogfoodNativeTargetId === target.id && styles.dogfoodChoiceSelected,
|
|
1368
|
+
!target.enabled && styles.dogfoodChoiceDisabled,
|
|
1369
|
+
]}
|
|
1370
|
+
>
|
|
1371
|
+
<Text style={[styles.dogfoodChoiceText, dogfoodNativeTargetId === target.id && styles.dogfoodChoiceTextSelected]}>{target.label}</Text>
|
|
1372
|
+
<Text style={styles.dogfoodProjectDetail}>{target.enabled
|
|
1373
|
+
? [target.platform, target.displaySurface || target.surface, 'WebRTC'].filter(Boolean).join(' · ')
|
|
1374
|
+
: target.reason || 'Unavailable on this box'}</Text>
|
|
1375
|
+
</Pressable>
|
|
1376
|
+
))}
|
|
1377
|
+
</View>
|
|
1378
|
+
) : null}
|
|
1379
|
+
<Text style={styles.dogfoodWizardHint}>Logs will be labelled with their real source: remote browser build, Hermes host, iOS Simulator, Android emulator, or connected device.</Text>
|
|
1380
|
+
<ActionRow
|
|
1381
|
+
label="Start Dogfood"
|
|
1382
|
+
tint="#5645d8"
|
|
1383
|
+
onPress={() => void startDogfoodRuntime()}
|
|
1384
|
+
disabled={dogfoodStartBlocked}
|
|
1385
|
+
/>
|
|
1415
1386
|
</>
|
|
1416
1387
|
) : null}
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
options={dogfoodLaneChoices}
|
|
1420
|
-
selected={dogfoodLane}
|
|
1421
|
-
onSelect={(lane) => {
|
|
1422
|
-
setDogfoodLane(lane);
|
|
1423
|
-
const appId = YaverFeedback.getDogfoodOnboarding()?.appId;
|
|
1424
|
-
if (appId) void setPreferredDogfoodLane(appId, lane);
|
|
1425
|
-
}}
|
|
1426
|
-
/>
|
|
1427
|
-
<Text style={styles.dogfoodWizardHint}>
|
|
1428
|
-
{[preferredRunner || 'Choose a coding agent', preferredModel].filter(Boolean).join(' · ')}
|
|
1429
|
-
</Text>
|
|
1430
|
-
<ActionRow
|
|
1431
|
-
label={dogfoodRuntime && !['idle', 'ready', 'failed', 'stopped'].includes(dogfoodRuntime.phase) ? dogfoodRuntime.message : 'Start Dogfood'}
|
|
1432
|
-
tint="#818cf8"
|
|
1433
|
-
onPress={() => void startDogfoodRuntime()}
|
|
1434
|
-
disabled={dogfoodStartBlocked || !!(dogfoodRuntime && !['idle', 'ready', 'failed', 'stopped'].includes(dogfoodRuntime.phase))}
|
|
1435
|
-
busy={!!dogfoodRuntime && !['idle', 'ready', 'failed', 'stopped'].includes(dogfoodRuntime.phase)}
|
|
1436
|
-
/>
|
|
1437
|
-
{dogfoodRuntime ? (
|
|
1388
|
+
|
|
1389
|
+
{dogfoodSetupStage === 'runtime' && dogfoodRuntime ? (
|
|
1438
1390
|
<>
|
|
1391
|
+
<View style={styles.dogfoodStageHeader}>
|
|
1392
|
+
<View style={styles.dogfoodStageCopy}>
|
|
1393
|
+
<Text style={styles.dogfoodStepLabel}>Dogfooding</Text>
|
|
1394
|
+
<Text style={styles.dogfoodStageTitle}>{dogfoodSourceLabel}</Text>
|
|
1395
|
+
</View>
|
|
1396
|
+
<Pressable
|
|
1397
|
+
onPress={() => {
|
|
1398
|
+
void dogfoodControllerRef.current?.stop().catch(() => {});
|
|
1399
|
+
dogfoodControllerRef.current = null;
|
|
1400
|
+
setDogfoodRuntime(null);
|
|
1401
|
+
setDogfoodSetupStage('lane');
|
|
1402
|
+
}}
|
|
1403
|
+
style={styles.dogfoodSmallAction}
|
|
1404
|
+
>
|
|
1405
|
+
<Text style={styles.dogfoodSmallActionText}>Change</Text>
|
|
1406
|
+
</Pressable>
|
|
1407
|
+
</View>
|
|
1439
1408
|
<DogfoodLiveConsole
|
|
1440
1409
|
lane={dogfoodRuntime.project.lane}
|
|
1410
|
+
sourceLabel={dogfoodSourceLabel}
|
|
1441
1411
|
phase={dogfoodRuntime.phase}
|
|
1442
1412
|
message={dogfoodRuntime.message}
|
|
1443
1413
|
logs={dogfoodRuntime.logs}
|
|
1444
1414
|
failure={dogfoodRuntime.failure}
|
|
1415
|
+
colors={FEEDBACK_DOGFOOD_CONSOLE_COLORS}
|
|
1445
1416
|
/>
|
|
1446
1417
|
{dogfoodRuntime.result?.url ? (
|
|
1447
1418
|
<Pressable onPress={() => void Linking.openURL(dogfoodRuntime.result!.url!)} style={styles.dogfoodOpenPreview}>
|
|
@@ -1714,7 +1685,7 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1714
1685
|
? `${reloadAction.label}…`
|
|
1715
1686
|
: reloadAction.label
|
|
1716
1687
|
}
|
|
1717
|
-
tint={reloadAction.id === 'rebuild' ? '#
|
|
1688
|
+
tint={reloadAction.id === 'rebuild' ? '#0369a1' : '#9a5700'}
|
|
1718
1689
|
onPress={() => {
|
|
1719
1690
|
void handleReloadAction(reloadAction);
|
|
1720
1691
|
}}
|
|
@@ -1735,16 +1706,12 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1735
1706
|
</View>
|
|
1736
1707
|
|
|
1737
1708
|
<View style={[styles.tabContent, activeTab !== 'chat' && styles.hidden]}>
|
|
1738
|
-
{/*
|
|
1739
|
-
|
|
1740
|
-
like the Yaver mobile app's Vibing tab. Second
|
|
1741
|
-
tap (Send) fires /vibing/execute with the typed
|
|
1742
|
-
prompt + resolved bundle id so the agent routes
|
|
1743
|
-
to the right repo. */}
|
|
1709
|
+
{/* Chat creates the first task, then VibeChatScreen owns the
|
|
1710
|
+
transcript and every MCP-backed follow-up. */}
|
|
1744
1711
|
{!showVibeInput ? (
|
|
1745
1712
|
<ActionRow
|
|
1746
1713
|
label={action === 'vibing' ? 'Starting…' : 'Vibing'}
|
|
1747
|
-
tint="#
|
|
1714
|
+
tint="#5645d8"
|
|
1748
1715
|
onPress={handleVibingButton}
|
|
1749
1716
|
disabled={busy}
|
|
1750
1717
|
busy={action === 'vibing'}
|
|
@@ -1794,31 +1761,6 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1794
1761
|
</Text>
|
|
1795
1762
|
)}
|
|
1796
1763
|
|
|
1797
|
-
{/* Screenshot & Fix */}
|
|
1798
|
-
{!dogfoodActive ? <ActionRow
|
|
1799
|
-
label={action === 'capturing' ? 'Working…' : 'Screenshot & Fix'}
|
|
1800
|
-
tint="#22c55e"
|
|
1801
|
-
onPress={handleScreenshotAndFix}
|
|
1802
|
-
disabled={busy}
|
|
1803
|
-
busy={action === 'capturing'}
|
|
1804
|
-
/> : null}
|
|
1805
|
-
|
|
1806
|
-
{/* Deploy — opens an inline panel that talks to
|
|
1807
|
-
/fleet/deploy-options on the agent and lets the user
|
|
1808
|
-
pick TestFlight / Play / Both, then a machine to run
|
|
1809
|
-
it on. Capabilities (e.g. "Linux can't TestFlight")
|
|
1810
|
-
come from the agent's doctor probes — no client-side
|
|
1811
|
-
platform smarts here. */}
|
|
1812
|
-
{!dogfoodActive && (!showDeploy ? (
|
|
1813
|
-
<ActionRow
|
|
1814
|
-
label="Deploy"
|
|
1815
|
-
tint="#7f8cf7"
|
|
1816
|
-
onPress={() => setShowDeploy(true)}
|
|
1817
|
-
disabled={busy}
|
|
1818
|
-
/>
|
|
1819
|
-
) : (
|
|
1820
|
-
<DeployPanel onClose={() => setShowDeploy(false)} />
|
|
1821
|
-
))}
|
|
1822
1764
|
</View>
|
|
1823
1765
|
|
|
1824
1766
|
{progress !== null && (
|
|
@@ -1846,9 +1788,8 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1846
1788
|
<Text style={styles.cancelBtnText}>Cancel</Text>
|
|
1847
1789
|
</Pressable>
|
|
1848
1790
|
</ScrollView>
|
|
1849
|
-
</
|
|
1850
|
-
|
|
1851
|
-
</Pressable>
|
|
1791
|
+
</View>
|
|
1792
|
+
</View>
|
|
1852
1793
|
</Modal>
|
|
1853
1794
|
)}
|
|
1854
1795
|
{runnerAuthModal ? (
|
|
@@ -1913,8 +1854,17 @@ const styles = StyleSheet.create({
|
|
|
1913
1854
|
dogfoodChoiceRow: { flexDirection: 'row', gap: 7 },
|
|
1914
1855
|
dogfoodChoice: { borderRadius: 9, borderWidth: 1, borderColor: '#d8d8e3', backgroundColor: '#fff', paddingHorizontal: 10, paddingVertical: 8 },
|
|
1915
1856
|
dogfoodChoiceSelected: { borderColor: '#818cf8', backgroundColor: 'rgba(129,140,248,0.18)' },
|
|
1857
|
+
dogfoodChoiceDisabled: { opacity: 0.48 },
|
|
1916
1858
|
dogfoodChoiceText: { color: '#666671', fontSize: 12, fontWeight: '700' },
|
|
1917
1859
|
dogfoodChoiceTextSelected: { color: '#5645d8' },
|
|
1860
|
+
dogfoodExpandedPanel: { gap: 8, borderRadius: 11, borderWidth: 1, borderColor: '#d8d8e3', backgroundColor: 'rgba(255,255,255,0.72)', padding: 10 },
|
|
1861
|
+
dogfoodProjectChoice: { gap: 3, borderRadius: 9, borderWidth: 1, borderColor: '#d8d8e3', backgroundColor: '#fff', paddingHorizontal: 10, paddingVertical: 9 },
|
|
1862
|
+
dogfoodProjectDetail: { color: '#777783', fontSize: 10, lineHeight: 14 },
|
|
1863
|
+
dogfoodStageHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 10 },
|
|
1864
|
+
dogfoodStageCopy: { flex: 1, gap: 2 },
|
|
1865
|
+
dogfoodStageTitle: { color: '#222229', fontSize: 13, fontWeight: '700' },
|
|
1866
|
+
dogfoodSmallAction: { borderRadius: 8, borderWidth: 1, borderColor: '#d8d8e3', backgroundColor: '#fff', paddingHorizontal: 10, paddingVertical: 7 },
|
|
1867
|
+
dogfoodSmallActionText: { color: '#5645d8', fontSize: 11, fontWeight: '800' },
|
|
1918
1868
|
dogfoodConsole: { maxHeight: 260, gap: 4, borderRadius: 11, padding: 10, backgroundColor: '#15151b' },
|
|
1919
1869
|
dogfoodConsoleStatus: { color: '#a5b4fc', fontSize: 12, fontWeight: '800' },
|
|
1920
1870
|
dogfoodConsoleLine: { color: '#d1d5db', fontSize: 10, lineHeight: 14, fontFamily: Platform.select({ ios: 'Menlo', android: 'monospace', default: 'monospace' }) },
|
|
@@ -1927,7 +1877,7 @@ const styles = StyleSheet.create({
|
|
|
1927
1877
|
gap: 4,
|
|
1928
1878
|
},
|
|
1929
1879
|
reloadHint: {
|
|
1930
|
-
color: '#
|
|
1880
|
+
color: '#666671',
|
|
1931
1881
|
fontSize: 11,
|
|
1932
1882
|
lineHeight: 15,
|
|
1933
1883
|
paddingHorizontal: 4,
|
|
@@ -1959,7 +1909,7 @@ const styles = StyleSheet.create({
|
|
|
1959
1909
|
backgroundColor: 'transparent',
|
|
1960
1910
|
},
|
|
1961
1911
|
vibeCancelBtnText: {
|
|
1962
|
-
color: '#
|
|
1912
|
+
color: '#5f5f69',
|
|
1963
1913
|
fontSize: 14,
|
|
1964
1914
|
fontWeight: '600',
|
|
1965
1915
|
},
|
|
@@ -1967,7 +1917,7 @@ const styles = StyleSheet.create({
|
|
|
1967
1917
|
paddingHorizontal: 16,
|
|
1968
1918
|
paddingVertical: 8,
|
|
1969
1919
|
borderRadius: 8,
|
|
1970
|
-
backgroundColor: '#
|
|
1920
|
+
backgroundColor: '#5645d8',
|
|
1971
1921
|
minWidth: 72,
|
|
1972
1922
|
alignItems: 'center',
|
|
1973
1923
|
},
|
|
@@ -1977,7 +1927,7 @@ const styles = StyleSheet.create({
|
|
|
1977
1927
|
fontWeight: '700',
|
|
1978
1928
|
},
|
|
1979
1929
|
vibeTaskLine: {
|
|
1980
|
-
color: '#
|
|
1930
|
+
color: '#5645d8',
|
|
1981
1931
|
fontSize: 12,
|
|
1982
1932
|
marginTop: -4,
|
|
1983
1933
|
fontFamily: Platform.select({ ios: 'Menlo', android: 'monospace', default: 'monospace' }),
|
|
@@ -1987,10 +1937,7 @@ const styles = StyleSheet.create({
|
|
|
1987
1937
|
backgroundColor: 'rgba(0,0,0,0.55)',
|
|
1988
1938
|
justifyContent: 'flex-end',
|
|
1989
1939
|
},
|
|
1990
|
-
|
|
1991
|
-
width: '100%',
|
|
1992
|
-
justifyContent: 'flex-end',
|
|
1993
|
-
},
|
|
1940
|
+
backdrop: StyleSheet.absoluteFillObject,
|
|
1994
1941
|
modal: {
|
|
1995
1942
|
backgroundColor: '#f8f8fb',
|
|
1996
1943
|
borderTopLeftRadius: 22,
|
|
@@ -2049,7 +1996,7 @@ const styles = StyleSheet.create({
|
|
|
2049
1996
|
tabs: { flexDirection: 'row', gap: 6, padding: 3, borderRadius: 12, backgroundColor: '#ededf3' },
|
|
2050
1997
|
tab: { flex: 1, minHeight: 36, borderRadius: 9, alignItems: 'center', justifyContent: 'center' },
|
|
2051
1998
|
tabSelected: { backgroundColor: '#fff' },
|
|
2052
|
-
tabText: { color: '#
|
|
1999
|
+
tabText: { color: '#666671', fontSize: 12, fontWeight: '700' },
|
|
2053
2000
|
tabTextSelected: { color: '#6252e8' },
|
|
2054
2001
|
tabContent: { gap: 12 },
|
|
2055
2002
|
hidden: { display: 'none' },
|
|
@@ -2074,7 +2021,7 @@ const styles = StyleSheet.create({
|
|
|
2074
2021
|
},
|
|
2075
2022
|
machineRoutes: { flexDirection: 'row', gap: 8 },
|
|
2076
2023
|
machineRouteCard: { flex: 1, minWidth: 0, borderRadius: 11, padding: 10, gap: 3, backgroundColor: '#fff', borderWidth: 1, borderColor: '#e1e1e8' },
|
|
2077
|
-
machineRouteLabel: { color: '#
|
|
2024
|
+
machineRouteLabel: { color: '#666671', fontSize: 10, fontWeight: '700', textTransform: 'uppercase' },
|
|
2078
2025
|
machineRouteValue: { color: '#222229', fontSize: 12, fontWeight: '700' },
|
|
2079
2026
|
machineHeader: {
|
|
2080
2027
|
flexDirection: 'row',
|
|
@@ -2110,7 +2057,7 @@ const styles = StyleSheet.create({
|
|
|
2110
2057
|
letterSpacing: 0.8,
|
|
2111
2058
|
},
|
|
2112
2059
|
machineAction: {
|
|
2113
|
-
color: '#
|
|
2060
|
+
color: '#5645d8',
|
|
2114
2061
|
fontSize: 12,
|
|
2115
2062
|
fontWeight: '700',
|
|
2116
2063
|
},
|
|
@@ -2141,7 +2088,7 @@ const styles = StyleSheet.create({
|
|
|
2141
2088
|
},
|
|
2142
2089
|
runnerSectionSubtitle: {
|
|
2143
2090
|
marginTop: 2,
|
|
2144
|
-
color: '#
|
|
2091
|
+
color: '#666671',
|
|
2145
2092
|
fontSize: 12,
|
|
2146
2093
|
},
|
|
2147
2094
|
runnerRefreshBtn: {
|
|
@@ -2168,15 +2115,15 @@ const styles = StyleSheet.create({
|
|
|
2168
2115
|
},
|
|
2169
2116
|
runnerCardOk: {
|
|
2170
2117
|
borderColor: 'rgba(34,197,94,0.28)',
|
|
2171
|
-
backgroundColor: 'rgba(
|
|
2118
|
+
backgroundColor: 'rgba(34,197,94,0.08)',
|
|
2172
2119
|
},
|
|
2173
2120
|
runnerCardWarning: {
|
|
2174
2121
|
borderColor: 'rgba(251,191,36,0.28)',
|
|
2175
|
-
backgroundColor: 'rgba(
|
|
2122
|
+
backgroundColor: 'rgba(245,158,11,0.08)',
|
|
2176
2123
|
},
|
|
2177
2124
|
runnerCardError: {
|
|
2178
2125
|
borderColor: 'rgba(248,113,113,0.28)',
|
|
2179
|
-
backgroundColor: 'rgba(
|
|
2126
|
+
backgroundColor: 'rgba(239,68,68,0.07)',
|
|
2180
2127
|
},
|
|
2181
2128
|
runnerCardTop: {
|
|
2182
2129
|
flexDirection: 'row',
|
|
@@ -2195,13 +2142,13 @@ const styles = StyleSheet.create({
|
|
|
2195
2142
|
color: '#666671',
|
|
2196
2143
|
},
|
|
2197
2144
|
runnerCardStatusOk: {
|
|
2198
|
-
color: '#
|
|
2145
|
+
color: '#137a3f',
|
|
2199
2146
|
},
|
|
2200
2147
|
runnerCardStatusWarning: {
|
|
2201
|
-
color: '#
|
|
2148
|
+
color: '#9a5700',
|
|
2202
2149
|
},
|
|
2203
2150
|
runnerCardStatusError: {
|
|
2204
|
-
color: '#
|
|
2151
|
+
color: '#b42318',
|
|
2205
2152
|
},
|
|
2206
2153
|
runnerCardDetail: {
|
|
2207
2154
|
color: '#858590',
|
|
@@ -2212,26 +2159,26 @@ const styles = StyleSheet.create({
|
|
|
2212
2159
|
borderRadius: 10,
|
|
2213
2160
|
borderWidth: 1,
|
|
2214
2161
|
borderColor: 'rgba(129,140,248,0.35)',
|
|
2215
|
-
backgroundColor: 'rgba(
|
|
2162
|
+
backgroundColor: 'rgba(86,69,216,0.10)',
|
|
2216
2163
|
paddingHorizontal: 12,
|
|
2217
2164
|
paddingVertical: 8,
|
|
2218
2165
|
},
|
|
2219
2166
|
runnerActionBtnText: {
|
|
2220
|
-
color: '#
|
|
2167
|
+
color: '#5645d8',
|
|
2221
2168
|
fontSize: 12,
|
|
2222
2169
|
fontWeight: '700',
|
|
2223
2170
|
},
|
|
2224
|
-
runnerActionBtnSelected: { borderColor: '#
|
|
2171
|
+
runnerActionBtnSelected: { borderColor: '#6555df', backgroundColor: 'rgba(86,69,216,0.18)' },
|
|
2225
2172
|
routingSummary: { flexDirection: 'row', alignItems: 'center', gap: 8, paddingHorizontal: 2 },
|
|
2226
2173
|
routingSummaryLabel: { color: '#7c7c87', fontSize: 11, fontWeight: '600' },
|
|
2227
2174
|
routingSummaryValue: { flex: 1, color: '#6555df', fontSize: 12, fontWeight: '700', textAlign: 'right' },
|
|
2228
2175
|
modelChoiceRow: { gap: 6, paddingTop: 2 },
|
|
2229
2176
|
modelChoice: { borderRadius: 9, borderWidth: 1, borderColor: 'rgba(148,163,184,0.18)', paddingHorizontal: 9, paddingVertical: 6 },
|
|
2230
|
-
modelChoiceSelected: { borderColor: '#
|
|
2177
|
+
modelChoiceSelected: { borderColor: '#6555df', backgroundColor: 'rgba(86,69,216,0.12)' },
|
|
2231
2178
|
modelChoiceText: { color: '#73737e', fontSize: 11 },
|
|
2232
2179
|
modelChoiceTextSelected: { color: '#5e4ce6', fontWeight: '700' },
|
|
2233
2180
|
runnerSectionError: {
|
|
2234
|
-
color: '#
|
|
2181
|
+
color: '#b42318',
|
|
2235
2182
|
fontSize: 12,
|
|
2236
2183
|
lineHeight: 18,
|
|
2237
2184
|
},
|
|
@@ -2251,13 +2198,13 @@ const styles = StyleSheet.create({
|
|
|
2251
2198
|
borderRadius: 3,
|
|
2252
2199
|
},
|
|
2253
2200
|
toast: {
|
|
2254
|
-
color: '#
|
|
2201
|
+
color: '#137a3f',
|
|
2255
2202
|
fontSize: 13,
|
|
2256
2203
|
textAlign: 'center',
|
|
2257
2204
|
marginTop: 4,
|
|
2258
2205
|
},
|
|
2259
2206
|
error: {
|
|
2260
|
-
color: '#
|
|
2207
|
+
color: '#b42318',
|
|
2261
2208
|
fontSize: 12,
|
|
2262
2209
|
textAlign: 'center',
|
|
2263
2210
|
marginTop: 4,
|