yaver-feedback-react-native 0.9.9 → 0.9.11
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/DogfoodRuntime.d.ts +32 -1
- package/dist/DogfoodRuntime.js +94 -14
- package/dist/DogfoodSessionUi.d.ts +2 -0
- package/dist/DogfoodSessionUi.js +8 -4
- package/dist/FeedbackModal.js +263 -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/__tests__/DogfoodRuntime.test.js +59 -0
- package/dist/__tests__/FeedbackModalContract.test.d.ts +1 -0
- package/dist/__tests__/FeedbackModalContract.test.js +77 -0
- package/dist/__tests__/NativeDogfoodShortcut.test.js +23 -0
- package/dist/__tests__/P2PDogfoodDriver.test.js +4 -1
- package/dist/index.d.ts +6 -7
- package/dist/index.js +7 -7
- package/ios/YaverHotReload.swift +3 -4
- package/package.json +1 -1
- package/src/DogfoodRuntime.ts +129 -15
- package/src/DogfoodSessionUi.tsx +10 -4
- package/src/FeedbackModal.tsx +335 -372
- package/src/FeedbackModalTheme.ts +29 -0
- package/src/P2PClient.ts +3 -1
- package/src/P2PDogfoodDriver.ts +17 -0
- package/src/__tests__/DogfoodRuntime.test.ts +66 -0
- package/src/__tests__/FeedbackModalContract.test.ts +88 -0
- package/src/__tests__/NativeDogfoodShortcut.test.ts +25 -0
- package/src/__tests__/P2PDogfoodDriver.test.ts +4 -0
- package/src/index.ts +6 -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';
|
|
@@ -56,30 +40,28 @@ import {
|
|
|
56
40
|
import {
|
|
57
41
|
DogfoodController,
|
|
58
42
|
defaultDogfoodLane,
|
|
43
|
+
dogfoodLanePlan,
|
|
59
44
|
dogfoodLaneOptions,
|
|
60
45
|
type DogfoodLane,
|
|
61
46
|
type DogfoodSnapshot,
|
|
62
47
|
} from './DogfoodRuntime';
|
|
63
48
|
import { createP2PDogfoodDriver } from './P2PDogfoodDriver';
|
|
64
49
|
import { DogfoodLanePicker, DogfoodLiveConsole, DogfoodStatusRail } from './DogfoodSessionUi';
|
|
50
|
+
import type { DogfoodRemoteRuntimeTarget } from './P2PClient';
|
|
51
|
+
import {
|
|
52
|
+
FEEDBACK_DOGFOOD_CONSOLE_COLORS,
|
|
53
|
+
FEEDBACK_DOGFOOD_LIGHT_COLORS,
|
|
54
|
+
} from './FeedbackModalTheme';
|
|
65
55
|
|
|
66
56
|
/**
|
|
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.
|
|
57
|
+
* Feedback modal with one conversational control surface. Authenticated users
|
|
58
|
+
* land directly in Chat; screenshots, fixes, and deploy intent are expressed
|
|
59
|
+
* in that conversation and handled by the connected agent/MCP toolchain.
|
|
77
60
|
*/
|
|
78
61
|
|
|
79
62
|
type ActionState =
|
|
80
63
|
| 'idle'
|
|
81
64
|
| 'hot-reloading'
|
|
82
|
-
| 'capturing'
|
|
83
65
|
| 'vibing';
|
|
84
66
|
|
|
85
67
|
type MachineCardState = {
|
|
@@ -122,8 +104,20 @@ type DogfoodProjectChoice = {
|
|
|
122
104
|
framework?: string;
|
|
123
105
|
frameworks?: string[];
|
|
124
106
|
surfaces?: string[];
|
|
107
|
+
branch?: string;
|
|
108
|
+
gitRemote?: string;
|
|
125
109
|
};
|
|
126
110
|
|
|
111
|
+
type DogfoodSetupStage = 'setup' | 'lane' | 'runtime';
|
|
112
|
+
type DogfoodExpandedStep = 'runner' | 'checkout' | null;
|
|
113
|
+
|
|
114
|
+
function dogfoodCheckoutDetail(project: DogfoodProjectChoice | null): string {
|
|
115
|
+
if (!project) return 'Choose a Git checkout on the selected machine';
|
|
116
|
+
return [project.path, project.branch ? `branch ${project.branch}` : project.gitRemote ? 'Git checkout' : 'checkout']
|
|
117
|
+
.filter(Boolean)
|
|
118
|
+
.join(' · ');
|
|
119
|
+
}
|
|
120
|
+
|
|
127
121
|
const PRIMARY_RUNNER_IDS = ['claude', 'codex', 'opencode'] as const;
|
|
128
122
|
|
|
129
123
|
function normalizeRunnerStatusRows(rows: RunnerAuthStatusRow[]): RunnerCardState[] {
|
|
@@ -261,18 +255,13 @@ export const FeedbackModal: React.FC = () => {
|
|
|
261
255
|
// Which reload action is in flight, so only that button spins.
|
|
262
256
|
const [reloadingId, setReloadingId] = useState<string | null>(null);
|
|
263
257
|
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).
|
|
258
|
+
// Signed-in users get the composer immediately. Signed-out users still get
|
|
259
|
+
// an explicit entry action so auth/setup failures remain named and visible.
|
|
269
260
|
const [showVibeInput, setShowVibeInput] = useState(false);
|
|
270
|
-
const [showDeploy, setShowDeploy] = useState(false);
|
|
271
261
|
const [vibePrompt, setVibePrompt] = useState('');
|
|
272
262
|
const [lastVibeTaskId, setLastVibeTaskId] = useState<string | null>(null);
|
|
273
263
|
const [quickIconColorPreset, setQuickIconColorPreset] =
|
|
274
264
|
useState<QuickIconColorPreset | null>(null);
|
|
275
|
-
const [keyboardInset, setKeyboardInset] = useState(0);
|
|
276
265
|
const [machineCard, setMachineCard] = useState<MachineCardState>({
|
|
277
266
|
device: null,
|
|
278
267
|
reachable: null,
|
|
@@ -300,6 +289,11 @@ export const FeedbackModal: React.FC = () => {
|
|
|
300
289
|
const [dogfoodProject, setDogfoodProject] = useState<DogfoodProjectChoice | null>(null);
|
|
301
290
|
const [dogfoodLane, setDogfoodLane] = useState<DogfoodLane>('browser');
|
|
302
291
|
const [dogfoodNativeAvailable, setDogfoodNativeAvailable] = useState(false);
|
|
292
|
+
const [dogfoodBrowserAvailable, setDogfoodBrowserAvailable] = useState(false);
|
|
293
|
+
const [dogfoodNativeTargets, setDogfoodNativeTargets] = useState<DogfoodRemoteRuntimeTarget[]>([]);
|
|
294
|
+
const [dogfoodNativeTargetId, setDogfoodNativeTargetId] = useState('');
|
|
295
|
+
const [dogfoodSetupStage, setDogfoodSetupStage] = useState<DogfoodSetupStage>('setup');
|
|
296
|
+
const [dogfoodExpandedStep, setDogfoodExpandedStep] = useState<DogfoodExpandedStep>(null);
|
|
303
297
|
const [dogfoodRuntime, setDogfoodRuntime] = useState<DogfoodSnapshot | null>(null);
|
|
304
298
|
const [dogfoodSetupLoading, setDogfoodSetupLoading] = useState(false);
|
|
305
299
|
const dogfoodControllerRef = useRef<DogfoodController | null>(null);
|
|
@@ -330,12 +324,25 @@ export const FeedbackModal: React.FC = () => {
|
|
|
330
324
|
if (preferred) {
|
|
331
325
|
const framework = preferred.framework || onboarding.framework || 'expo';
|
|
332
326
|
const capabilities = await client.getDogfoodRemoteRuntimeCapabilities(preferred.path, framework).catch(() => null);
|
|
333
|
-
const
|
|
334
|
-
|
|
327
|
+
const targets = capabilities?.targets || [];
|
|
328
|
+
const nativeTargets = targets.filter((target) => target.id !== 'browser-window');
|
|
329
|
+
const enabledNativeTargets = nativeTargets.filter((target) => target.enabled);
|
|
330
|
+
const nativeRuntimeAvailable = enabledNativeTargets.length > 0;
|
|
331
|
+
const browserRuntimeAvailable = targets.some((target) => target.enabled && target.id === 'browser-window');
|
|
332
|
+
if (mountedRef.current) {
|
|
333
|
+
setDogfoodNativeAvailable(nativeRuntimeAvailable);
|
|
334
|
+
setDogfoodBrowserAvailable(browserRuntimeAvailable);
|
|
335
|
+
setDogfoodNativeTargets(nativeTargets);
|
|
336
|
+
setDogfoodNativeTargetId((current) => enabledNativeTargets.some((target) => target.id === current)
|
|
337
|
+
? current
|
|
338
|
+
: enabledNativeTargets[0]?.id || '');
|
|
339
|
+
}
|
|
335
340
|
const savedLane = await getPreferredDogfoodLane(onboarding.appId);
|
|
336
|
-
const savedSupported = dogfoodLaneOptions(framework, { nativeRuntimeAvailable })
|
|
341
|
+
const savedSupported = dogfoodLaneOptions(framework, { nativeRuntimeAvailable, browserRuntimeAvailable })
|
|
337
342
|
.some((option) => option.lane === savedLane && option.supported);
|
|
338
|
-
setDogfoodLane(savedLane && savedSupported
|
|
343
|
+
setDogfoodLane(savedLane && savedSupported
|
|
344
|
+
? savedLane
|
|
345
|
+
: defaultDogfoodLane(framework, { nativeRuntimeAvailable, browserRuntimeAvailable }));
|
|
339
346
|
}
|
|
340
347
|
} catch (cause) {
|
|
341
348
|
if (mountedRef.current) setDogfoodEnrollment({ status: 'failed', error: cause instanceof Error ? cause.message : String(cause) });
|
|
@@ -347,12 +354,31 @@ export const FeedbackModal: React.FC = () => {
|
|
|
347
354
|
useEffect(() => {
|
|
348
355
|
let cancelled = false;
|
|
349
356
|
setDogfoodNativeAvailable(false);
|
|
357
|
+
setDogfoodBrowserAvailable(false);
|
|
358
|
+
setDogfoodNativeTargets([]);
|
|
359
|
+
setDogfoodNativeTargetId('');
|
|
350
360
|
const client = YaverFeedback.getP2PClient();
|
|
351
361
|
if (!client || !dogfoodProject) return () => { cancelled = true; };
|
|
352
362
|
const framework = dogfoodProject.framework || YaverFeedback.getDogfoodOnboarding()?.framework || 'expo';
|
|
353
363
|
void client.getDogfoodRemoteRuntimeCapabilities(dogfoodProject.path, framework)
|
|
354
364
|
.then((value) => {
|
|
355
|
-
if (
|
|
365
|
+
if (cancelled) return;
|
|
366
|
+
const nativeTargets = value.targets.filter((target) => target.id !== 'browser-window');
|
|
367
|
+
const enabledNativeTargets = nativeTargets.filter((target) => target.enabled);
|
|
368
|
+
setDogfoodNativeAvailable(enabledNativeTargets.length > 0);
|
|
369
|
+
setDogfoodBrowserAvailable(value.targets.some((target) => target.enabled && target.id === 'browser-window'));
|
|
370
|
+
setDogfoodNativeTargets(nativeTargets);
|
|
371
|
+
setDogfoodNativeTargetId((current) => enabledNativeTargets.some((target) => target.id === current)
|
|
372
|
+
? current
|
|
373
|
+
: enabledNativeTargets[0]?.id || '');
|
|
374
|
+
const laneCapabilities = {
|
|
375
|
+
nativeRuntimeAvailable: enabledNativeTargets.length > 0,
|
|
376
|
+
browserRuntimeAvailable: value.targets.some((target) => target.enabled && target.id === 'browser-window'),
|
|
377
|
+
};
|
|
378
|
+
setDogfoodLane((current) => dogfoodLaneOptions(framework, laneCapabilities)
|
|
379
|
+
.some((option) => option.lane === current && option.supported)
|
|
380
|
+
? current
|
|
381
|
+
: defaultDogfoodLane(framework, laneCapabilities));
|
|
356
382
|
})
|
|
357
383
|
.catch(() => {});
|
|
358
384
|
return () => { cancelled = true; };
|
|
@@ -364,18 +390,25 @@ export const FeedbackModal: React.FC = () => {
|
|
|
364
390
|
if (!client || !dogfoodProject || !onboarding) return;
|
|
365
391
|
await dogfoodControllerRef.current?.stop().catch(() => {});
|
|
366
392
|
const framework = dogfoodProject.framework || onboarding.framework || 'expo';
|
|
393
|
+
const lanePlan = dogfoodLanePlan(framework, {
|
|
394
|
+
nativeRuntimeAvailable: dogfoodNativeAvailable,
|
|
395
|
+
browserRuntimeAvailable: dogfoodBrowserAvailable,
|
|
396
|
+
}, dogfoodLane);
|
|
367
397
|
const controller = new DogfoodController({
|
|
368
398
|
name: dogfoodProject.name,
|
|
369
399
|
workDir: dogfoodProject.path,
|
|
370
400
|
framework,
|
|
371
|
-
lane:
|
|
401
|
+
lane: lanePlan.preferred,
|
|
402
|
+
fallbackLane: lanePlan.fallback,
|
|
403
|
+
nativeTargetId: lanePlan.preferred === 'webrtc' ? dogfoodNativeTargetId : undefined,
|
|
372
404
|
}, createP2PDogfoodDriver(client), {
|
|
373
405
|
onChange: (snapshot) => { if (mountedRef.current) setDogfoodRuntime(snapshot); },
|
|
374
406
|
});
|
|
375
407
|
dogfoodControllerRef.current = controller;
|
|
376
408
|
setDogfoodRuntime(controller.snapshot());
|
|
409
|
+
setDogfoodSetupStage('runtime');
|
|
377
410
|
await controller.trigger().catch(() => {});
|
|
378
|
-
}, [dogfoodLane, dogfoodProject]);
|
|
411
|
+
}, [dogfoodLane, dogfoodNativeTargetId, dogfoodProject]);
|
|
379
412
|
|
|
380
413
|
/**
|
|
381
414
|
* Ask the machine what its dev server is doing, so the reload actions can
|
|
@@ -571,16 +604,24 @@ export const FeedbackModal: React.FC = () => {
|
|
|
571
604
|
// user's feedback preference merely to open Developer Mode.
|
|
572
605
|
if (YaverFeedback.isEnabled() || onboarding) {
|
|
573
606
|
const directDogfood = YaverFeedback.getDogfoodStatus().active;
|
|
607
|
+
const authenticated = YaverFeedback.isAuthed();
|
|
574
608
|
setDogfoodActive(directDogfood);
|
|
575
609
|
setVisible(true);
|
|
576
610
|
setError(null);
|
|
577
611
|
setToast(null);
|
|
578
612
|
setProgress(null);
|
|
579
613
|
setAction('idle');
|
|
580
|
-
setShowVibeInput(directDogfood);
|
|
614
|
+
setShowVibeInput(authenticated || directDogfood);
|
|
581
615
|
setVibePrompt('');
|
|
582
616
|
if (onboarding) {
|
|
617
|
+
// A Dogfood shortcut is an explicit setup/runtime intent. Opening on
|
|
618
|
+
// Chat hid the machine/runner/checkout gate for signed-in SFMG users;
|
|
619
|
+
// keep the SDK-owned Dogfood surface visible, then show its live logs
|
|
620
|
+
// immediately when Start is tapped.
|
|
583
621
|
setActiveTab('settings');
|
|
622
|
+
setDogfoodSetupStage('setup');
|
|
623
|
+
setDogfoodExpandedStep(null);
|
|
624
|
+
setDogfoodRuntime(null);
|
|
584
625
|
void loadDogfoodOnboarding();
|
|
585
626
|
}
|
|
586
627
|
// Re-read the "user hid the quick icon" flag on every open so
|
|
@@ -652,31 +693,13 @@ export const FeedbackModal: React.FC = () => {
|
|
|
652
693
|
return () => clearInterval(interval);
|
|
653
694
|
}, [loadRunnerStatuses, loadSelectedMachine, refreshDevSnapshot, visible]);
|
|
654
695
|
|
|
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
696
|
useEffect(() => {
|
|
676
697
|
if (!visible || !showVibeInput) return;
|
|
677
|
-
|
|
698
|
+
// UIKit owns keyboard insets on the ScrollView. This one bounded scroll only
|
|
699
|
+
// reveals the newly mounted composer; it does not add a second inset.
|
|
700
|
+
const timer = setTimeout(() => scrollRef.current?.scrollToEnd({ animated: true }), 80);
|
|
678
701
|
return () => clearTimeout(timer);
|
|
679
|
-
}, [
|
|
702
|
+
}, [showVibeInput, visible]);
|
|
680
703
|
|
|
681
704
|
const closeSoon = useCallback((delayMs = 1200) => {
|
|
682
705
|
setTimeout(() => {
|
|
@@ -693,6 +716,9 @@ export const FeedbackModal: React.FC = () => {
|
|
|
693
716
|
setShowVibeInput(false);
|
|
694
717
|
setVibePrompt('');
|
|
695
718
|
setRunnerStatusError(null);
|
|
719
|
+
setDogfoodSetupStage('setup');
|
|
720
|
+
setDogfoodExpandedStep(null);
|
|
721
|
+
setDogfoodRuntime(null);
|
|
696
722
|
void dogfoodControllerRef.current?.stop().catch(() => {});
|
|
697
723
|
dogfoodControllerRef.current = null;
|
|
698
724
|
YaverFeedback.clearDogfoodOnboarding();
|
|
@@ -886,123 +912,7 @@ export const FeedbackModal: React.FC = () => {
|
|
|
886
912
|
}
|
|
887
913
|
}, [closeSoon, loadSelectedMachine, runWithReconnect]);
|
|
888
914
|
|
|
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 ─────────────────────────────────────────────────────
|
|
915
|
+
// ─── Chat ──────────────────────────────────────────────────────────
|
|
1006
916
|
// First tap expands the input; second submit fires the actual
|
|
1007
917
|
// /vibing/execute. Mirrors the Yaver mobile app's Vibing tab —
|
|
1008
918
|
// user types what they want, hits Send, sees the task id back. If
|
|
@@ -1151,45 +1061,59 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1151
1061
|
&& (selectedDogfoodRunner.ready || selectedDogfoodRunner.authConfigured);
|
|
1152
1062
|
const dogfoodModelReady = !selectedDogfoodRunner?.models?.length
|
|
1153
1063
|
|| !!preferredModel && selectedDogfoodRunner.models.some((model) => model.id === preferredModel);
|
|
1154
|
-
const
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
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
|
-
},
|
|
1064
|
+
const dogfoodFramework = dogfoodProject?.framework || YaverFeedback.getDogfoodOnboarding()?.framework || 'expo';
|
|
1065
|
+
const dogfoodLaneChoices = dogfoodLaneOptions(dogfoodFramework, {
|
|
1066
|
+
nativeRuntimeAvailable: dogfoodNativeAvailable,
|
|
1067
|
+
browserRuntimeAvailable: dogfoodBrowserAvailable,
|
|
1068
|
+
});
|
|
1069
|
+
const dogfoodLanePolicy = dogfoodLanePlan(dogfoodFramework, {
|
|
1070
|
+
nativeRuntimeAvailable: dogfoodNativeAvailable,
|
|
1071
|
+
browserRuntimeAvailable: dogfoodBrowserAvailable,
|
|
1072
|
+
}, dogfoodLane);
|
|
1073
|
+
const selectedDogfoodNativeTarget = dogfoodNativeTargets.find((target) => target.id === dogfoodNativeTargetId) || null;
|
|
1074
|
+
const dogfoodLaneReady = dogfoodLaneChoices.some((option) => option.lane === dogfoodLane && option.supported)
|
|
1075
|
+
&& (dogfoodLane !== 'webrtc' || !!selectedDogfoodNativeTarget?.enabled);
|
|
1076
|
+
const dogfoodMachineReady = !!machineCard.device && machineCard.status === 'live';
|
|
1077
|
+
const dogfoodSetupReady = dogfoodMachineReady && !!dogfoodProject && dogfoodRunnerReady && dogfoodModelReady;
|
|
1078
|
+
const dogfoodSetupSteps = [
|
|
1176
1079
|
{
|
|
1177
|
-
key: '
|
|
1178
|
-
|
|
1179
|
-
|
|
1080
|
+
key: 'box',
|
|
1081
|
+
label: 'Remote box',
|
|
1082
|
+
detail: dogfoodMachineReady ? machineCard.title : 'Choose a reachable development machine',
|
|
1083
|
+
tone: dogfoodMachineReady ? 'ready' as const : 'attention' as const,
|
|
1084
|
+
actionLabel: machineCard.device ? 'Change' : 'Pick',
|
|
1085
|
+
onAction: () => YaverFeedback.showMachinePicker(),
|
|
1180
1086
|
},
|
|
1181
1087
|
{
|
|
1182
|
-
key: '
|
|
1183
|
-
|
|
1184
|
-
|
|
1088
|
+
key: 'runner',
|
|
1089
|
+
label: 'Runner',
|
|
1090
|
+
detail: dogfoodRunnerReady
|
|
1091
|
+
? [selectedDogfoodRunner?.name || preferredRunner || 'Ready', preferredModel || 'default model'].join(' · ')
|
|
1092
|
+
: 'Choose or configure a coding runner',
|
|
1093
|
+
tone: dogfoodRunnerReady && dogfoodModelReady ? 'ready' as const : 'attention' as const,
|
|
1094
|
+
actionLabel: dogfoodExpandedStep === 'runner' ? 'Done' : dogfoodRunnerReady ? 'Change' : 'Choose',
|
|
1095
|
+
expanded: dogfoodExpandedStep === 'runner',
|
|
1096
|
+
onAction: () => setDogfoodExpandedStep((current) => current === 'runner' ? null : 'runner'),
|
|
1185
1097
|
},
|
|
1186
1098
|
{
|
|
1187
|
-
key: '
|
|
1188
|
-
|
|
1189
|
-
|
|
1099
|
+
key: 'checkout',
|
|
1100
|
+
label: 'Checkout',
|
|
1101
|
+
detail: dogfoodCheckoutDetail(dogfoodProject),
|
|
1102
|
+
tone: dogfoodProject ? 'ready' as const : 'attention' as const,
|
|
1103
|
+
actionLabel: dogfoodExpandedStep === 'checkout' ? 'Done' : dogfoodProject ? 'Change' : 'Choose',
|
|
1104
|
+
expanded: dogfoodExpandedStep === 'checkout',
|
|
1105
|
+
onAction: () => setDogfoodExpandedStep((current) => current === 'checkout' ? null : 'checkout'),
|
|
1190
1106
|
},
|
|
1191
1107
|
];
|
|
1192
|
-
const dogfoodStartBlocked = !
|
|
1108
|
+
const dogfoodStartBlocked = !dogfoodSetupReady || !dogfoodLaneReady;
|
|
1109
|
+
const activeDogfoodLane = dogfoodRuntime?.project.lane || dogfoodLane;
|
|
1110
|
+
const dogfoodSourceLabel = activeDogfoodLane === 'webrtc'
|
|
1111
|
+
? selectedDogfoodNativeTarget
|
|
1112
|
+
? [selectedDogfoodNativeTarget.label, selectedDogfoodNativeTarget.platform].filter(Boolean).join(' · ')
|
|
1113
|
+
: 'Native simulator, emulator, or device'
|
|
1114
|
+
: activeDogfoodLane === 'hermes'
|
|
1115
|
+
? `Hermes build · ${machineCard.title}`
|
|
1116
|
+
: `${dogfoodFramework === 'flutter' ? 'Flutter web compiler' : 'Metro / browser build'} · ${machineCard.title}`;
|
|
1193
1117
|
|
|
1194
1118
|
// Once the user fires off a vibe task, swap the entire modal body
|
|
1195
1119
|
// for the live chat screen. The chat manages its own SSE
|
|
@@ -1252,14 +1176,9 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1252
1176
|
transparent
|
|
1253
1177
|
onRequestClose={handleClose}
|
|
1254
1178
|
>
|
|
1255
|
-
<
|
|
1256
|
-
<
|
|
1257
|
-
|
|
1258
|
-
keyboardVerticalOffset={Platform.OS === 'ios' ? 12 : 0}
|
|
1259
|
-
style={styles.kbAvoider}
|
|
1260
|
-
pointerEvents="box-none"
|
|
1261
|
-
>
|
|
1262
|
-
<Pressable
|
|
1179
|
+
<View style={styles.overlay}>
|
|
1180
|
+
<Pressable style={styles.backdrop} onPress={handleClose} accessibilityLabel="Close feedback" />
|
|
1181
|
+
<View
|
|
1263
1182
|
// Tablet: cap modal width and center as a card-style
|
|
1264
1183
|
// sheet rather than a phone bottom sheet that stretches
|
|
1265
1184
|
// across a 12.9" iPad. Phone behaviour unchanged.
|
|
@@ -1275,20 +1194,11 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1275
1194
|
}
|
|
1276
1195
|
: null,
|
|
1277
1196
|
]}
|
|
1278
|
-
onPress={(e) => {
|
|
1279
|
-
e.stopPropagation();
|
|
1280
|
-
Keyboard.dismiss();
|
|
1281
|
-
}}
|
|
1282
1197
|
>
|
|
1283
1198
|
<ScrollView
|
|
1284
1199
|
ref={scrollRef}
|
|
1285
1200
|
style={styles.scroll}
|
|
1286
|
-
contentContainerStyle={
|
|
1287
|
-
styles.scrollContent,
|
|
1288
|
-
showVibeInput && keyboardInset > 0
|
|
1289
|
-
? { paddingBottom: 8 + keyboardInset }
|
|
1290
|
-
: null,
|
|
1291
|
-
]}
|
|
1201
|
+
contentContainerStyle={styles.scrollContent}
|
|
1292
1202
|
keyboardShouldPersistTaps="handled"
|
|
1293
1203
|
keyboardDismissMode={Platform.OS === 'ios' ? 'interactive' : 'on-drag'}
|
|
1294
1204
|
contentInsetAdjustmentBehavior="always"
|
|
@@ -1329,10 +1239,17 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1329
1239
|
<View style={styles.dogfoodWizard}>
|
|
1330
1240
|
<Text style={styles.dogfoodWizardTitle}>Dogfood this app</Text>
|
|
1331
1241
|
<Text style={styles.dogfoodWizardHint}>
|
|
1332
|
-
|
|
1242
|
+
{dogfoodEnrollment?.status === 'active'
|
|
1243
|
+
? 'Signed in · this installation is approved'
|
|
1244
|
+
: `Signed in · installation ${dogfoodEnrollment?.status || 'checking'}`}
|
|
1333
1245
|
</Text>
|
|
1334
|
-
|
|
1335
|
-
|
|
1246
|
+
{dogfoodEnrollment?.status === 'active' && dogfoodSetupStage === 'setup' ? (
|
|
1247
|
+
<DogfoodStatusRail
|
|
1248
|
+
steps={dogfoodSetupSteps}
|
|
1249
|
+
colors={FEEDBACK_DOGFOOD_LIGHT_COLORS}
|
|
1250
|
+
/>
|
|
1251
|
+
) : null}
|
|
1252
|
+
{dogfoodEnrollment?.status !== 'active' && dogfoodEnrollment?.installationId ? (
|
|
1336
1253
|
<Text selectable style={styles.dogfoodInstallationId}>
|
|
1337
1254
|
This device · {dogfoodEnrollment.installationId}
|
|
1338
1255
|
</Text>
|
|
@@ -1352,96 +1269,166 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1352
1269
|
</View>
|
|
1353
1270
|
) : (
|
|
1354
1271
|
<>
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1272
|
+
{dogfoodSetupStage === 'setup' ? (
|
|
1273
|
+
<>
|
|
1274
|
+
{dogfoodExpandedStep === 'runner' ? (
|
|
1275
|
+
<View style={styles.dogfoodExpandedPanel}>
|
|
1276
|
+
<Text style={styles.dogfoodStepLabel}>Coding runner</Text>
|
|
1277
|
+
<View style={styles.dogfoodChoiceRow}>
|
|
1278
|
+
{runnerCards.filter((row) => row.ready || row.authConfigured).map((row) => (
|
|
1279
|
+
<Pressable
|
|
1280
|
+
key={row.id}
|
|
1281
|
+
onPress={() => {
|
|
1282
|
+
const nextModel = row.models?.find((model) => model.isDefault)?.id || row.models?.[0]?.id || '';
|
|
1283
|
+
setPreferredRunnerState(row.id);
|
|
1284
|
+
setPreferredModelState(nextModel);
|
|
1285
|
+
void setPreferredRunner(row.id);
|
|
1286
|
+
void setPreferredModel(nextModel || null);
|
|
1287
|
+
}}
|
|
1288
|
+
style={[styles.dogfoodChoice, preferredRunner === row.id && styles.dogfoodChoiceSelected]}
|
|
1289
|
+
accessibilityRole="button"
|
|
1290
|
+
accessibilityState={{ selected: preferredRunner === row.id }}
|
|
1291
|
+
>
|
|
1292
|
+
<Text style={[styles.dogfoodChoiceText, preferredRunner === row.id && styles.dogfoodChoiceTextSelected]}>{row.name}</Text>
|
|
1293
|
+
</Pressable>
|
|
1294
|
+
))}
|
|
1295
|
+
</View>
|
|
1296
|
+
{readyRunnerCount === 0 ? (
|
|
1297
|
+
<Text style={styles.dogfoodWizardHint}>Configure a coding runner below, then retry.</Text>
|
|
1298
|
+
) : null}
|
|
1299
|
+
{selectedDogfoodRunner?.models?.length ? (
|
|
1300
|
+
<>
|
|
1301
|
+
<Text style={styles.dogfoodStepLabel}>Model</Text>
|
|
1302
|
+
<View style={styles.dogfoodChoiceRow}>
|
|
1303
|
+
{selectedDogfoodRunner.models.map((model) => (
|
|
1304
|
+
<Pressable
|
|
1305
|
+
key={model.id}
|
|
1306
|
+
onPress={() => {
|
|
1307
|
+
setPreferredModelState(model.id);
|
|
1308
|
+
void setPreferredModel(model.id);
|
|
1309
|
+
}}
|
|
1310
|
+
style={[styles.dogfoodChoice, preferredModel === model.id && styles.dogfoodChoiceSelected]}
|
|
1311
|
+
>
|
|
1312
|
+
<Text style={[styles.dogfoodChoiceText, preferredModel === model.id && styles.dogfoodChoiceTextSelected]}>{model.name || model.id}</Text>
|
|
1313
|
+
</Pressable>
|
|
1314
|
+
))}
|
|
1315
|
+
</View>
|
|
1316
|
+
</>
|
|
1317
|
+
) : null}
|
|
1318
|
+
</View>
|
|
1319
|
+
) : null}
|
|
1320
|
+
{dogfoodExpandedStep === 'checkout' ? (
|
|
1321
|
+
<View style={styles.dogfoodExpandedPanel}>
|
|
1322
|
+
<Text style={styles.dogfoodStepLabel}>Git checkout on remote box</Text>
|
|
1323
|
+
{dogfoodProjects.map((project) => (
|
|
1324
|
+
<Pressable
|
|
1325
|
+
key={project.path}
|
|
1326
|
+
onPress={() => {
|
|
1327
|
+
setDogfoodProject(project);
|
|
1328
|
+
setDogfoodLane(defaultDogfoodLane(project.framework || YaverFeedback.getDogfoodOnboarding()?.framework || 'expo'));
|
|
1329
|
+
setDogfoodRuntime(null);
|
|
1330
|
+
}}
|
|
1331
|
+
style={[styles.dogfoodProjectChoice, dogfoodProject?.path === project.path && styles.dogfoodChoiceSelected]}
|
|
1332
|
+
>
|
|
1333
|
+
<Text style={[styles.dogfoodChoiceText, dogfoodProject?.path === project.path && styles.dogfoodChoiceTextSelected]}>{project.name}</Text>
|
|
1334
|
+
<Text style={styles.dogfoodProjectDetail}>{dogfoodCheckoutDetail(project)}</Text>
|
|
1335
|
+
</Pressable>
|
|
1336
|
+
))}
|
|
1337
|
+
</View>
|
|
1338
|
+
) : null}
|
|
1339
|
+
<ActionRow
|
|
1340
|
+
label="Choose runtime"
|
|
1341
|
+
tint="#5645d8"
|
|
1376
1342
|
onPress={() => {
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
setPreferredModelState(nextModel);
|
|
1380
|
-
void setPreferredRunner(row.id);
|
|
1381
|
-
void setPreferredModel(nextModel || null);
|
|
1343
|
+
setDogfoodExpandedStep(null);
|
|
1344
|
+
setDogfoodSetupStage('lane');
|
|
1382
1345
|
}}
|
|
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>
|
|
1346
|
+
disabled={!dogfoodSetupReady}
|
|
1347
|
+
/>
|
|
1348
|
+
</>
|
|
1394
1349
|
) : null}
|
|
1395
|
-
|
|
1350
|
+
|
|
1351
|
+
{dogfoodSetupStage === 'lane' ? (
|
|
1396
1352
|
<>
|
|
1397
|
-
<
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
<
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1353
|
+
<View style={styles.dogfoodStageHeader}>
|
|
1354
|
+
<View style={styles.dogfoodStageCopy}>
|
|
1355
|
+
<Text style={styles.dogfoodStepLabel}>Runtime</Text>
|
|
1356
|
+
<Text style={styles.dogfoodStageTitle}>{dogfoodProject?.name} · {dogfoodFramework}</Text>
|
|
1357
|
+
</View>
|
|
1358
|
+
<Pressable onPress={() => setDogfoodSetupStage('setup')} style={styles.dogfoodSmallAction}>
|
|
1359
|
+
<Text style={styles.dogfoodSmallActionText}>Back</Text>
|
|
1360
|
+
</Pressable>
|
|
1361
|
+
</View>
|
|
1362
|
+
<DogfoodLanePicker
|
|
1363
|
+
options={dogfoodLaneChoices}
|
|
1364
|
+
selected={dogfoodLane}
|
|
1365
|
+
fallbackLane={dogfoodLanePolicy.fallback}
|
|
1366
|
+
colors={FEEDBACK_DOGFOOD_LIGHT_COLORS}
|
|
1367
|
+
onSelect={(lane) => {
|
|
1368
|
+
setDogfoodLane(lane);
|
|
1369
|
+
const appId = YaverFeedback.getDogfoodOnboarding()?.appId;
|
|
1370
|
+
if (appId) void setPreferredDogfoodLane(appId, lane);
|
|
1371
|
+
}}
|
|
1372
|
+
/>
|
|
1373
|
+
{dogfoodLane === 'webrtc' ? (
|
|
1374
|
+
<View style={styles.dogfoodExpandedPanel}>
|
|
1375
|
+
<Text style={styles.dogfoodStepLabel}>Simulator, emulator, or device</Text>
|
|
1376
|
+
{dogfoodNativeTargets.map((target) => (
|
|
1377
|
+
<Pressable
|
|
1378
|
+
key={target.id}
|
|
1379
|
+
disabled={!target.enabled}
|
|
1380
|
+
onPress={() => setDogfoodNativeTargetId(target.id)}
|
|
1381
|
+
style={[
|
|
1382
|
+
styles.dogfoodProjectChoice,
|
|
1383
|
+
dogfoodNativeTargetId === target.id && styles.dogfoodChoiceSelected,
|
|
1384
|
+
!target.enabled && styles.dogfoodChoiceDisabled,
|
|
1385
|
+
]}
|
|
1386
|
+
>
|
|
1387
|
+
<Text style={[styles.dogfoodChoiceText, dogfoodNativeTargetId === target.id && styles.dogfoodChoiceTextSelected]}>{target.label}</Text>
|
|
1388
|
+
<Text style={styles.dogfoodProjectDetail}>{target.enabled
|
|
1389
|
+
? [target.platform, target.displaySurface || target.surface, 'WebRTC'].filter(Boolean).join(' · ')
|
|
1390
|
+
: target.reason || 'Unavailable on this box'}</Text>
|
|
1391
|
+
</Pressable>
|
|
1392
|
+
))}
|
|
1393
|
+
</View>
|
|
1394
|
+
) : null}
|
|
1395
|
+
<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>
|
|
1396
|
+
<ActionRow
|
|
1397
|
+
label="Start Dogfood"
|
|
1398
|
+
tint="#5645d8"
|
|
1399
|
+
onPress={() => void startDogfoodRuntime()}
|
|
1400
|
+
disabled={dogfoodStartBlocked}
|
|
1401
|
+
/>
|
|
1415
1402
|
</>
|
|
1416
1403
|
) : 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 ? (
|
|
1404
|
+
|
|
1405
|
+
{dogfoodSetupStage === 'runtime' && dogfoodRuntime ? (
|
|
1438
1406
|
<>
|
|
1407
|
+
<View style={styles.dogfoodStageHeader}>
|
|
1408
|
+
<View style={styles.dogfoodStageCopy}>
|
|
1409
|
+
<Text style={styles.dogfoodStepLabel}>Dogfooding</Text>
|
|
1410
|
+
<Text style={styles.dogfoodStageTitle}>{dogfoodSourceLabel}</Text>
|
|
1411
|
+
</View>
|
|
1412
|
+
<Pressable
|
|
1413
|
+
onPress={() => {
|
|
1414
|
+
void dogfoodControllerRef.current?.stop().catch(() => {});
|
|
1415
|
+
dogfoodControllerRef.current = null;
|
|
1416
|
+
setDogfoodRuntime(null);
|
|
1417
|
+
setDogfoodSetupStage('lane');
|
|
1418
|
+
}}
|
|
1419
|
+
style={styles.dogfoodSmallAction}
|
|
1420
|
+
>
|
|
1421
|
+
<Text style={styles.dogfoodSmallActionText}>Change</Text>
|
|
1422
|
+
</Pressable>
|
|
1423
|
+
</View>
|
|
1439
1424
|
<DogfoodLiveConsole
|
|
1440
1425
|
lane={dogfoodRuntime.project.lane}
|
|
1426
|
+
sourceLabel={dogfoodSourceLabel}
|
|
1441
1427
|
phase={dogfoodRuntime.phase}
|
|
1442
1428
|
message={dogfoodRuntime.message}
|
|
1443
1429
|
logs={dogfoodRuntime.logs}
|
|
1444
1430
|
failure={dogfoodRuntime.failure}
|
|
1431
|
+
colors={FEEDBACK_DOGFOOD_CONSOLE_COLORS}
|
|
1445
1432
|
/>
|
|
1446
1433
|
{dogfoodRuntime.result?.url ? (
|
|
1447
1434
|
<Pressable onPress={() => void Linking.openURL(dogfoodRuntime.result!.url!)} style={styles.dogfoodOpenPreview}>
|
|
@@ -1714,7 +1701,7 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1714
1701
|
? `${reloadAction.label}…`
|
|
1715
1702
|
: reloadAction.label
|
|
1716
1703
|
}
|
|
1717
|
-
tint={reloadAction.id === 'rebuild' ? '#
|
|
1704
|
+
tint={reloadAction.id === 'rebuild' ? '#0369a1' : '#9a5700'}
|
|
1718
1705
|
onPress={() => {
|
|
1719
1706
|
void handleReloadAction(reloadAction);
|
|
1720
1707
|
}}
|
|
@@ -1735,16 +1722,12 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1735
1722
|
</View>
|
|
1736
1723
|
|
|
1737
1724
|
<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. */}
|
|
1725
|
+
{/* Chat creates the first task, then VibeChatScreen owns the
|
|
1726
|
+
transcript and every MCP-backed follow-up. */}
|
|
1744
1727
|
{!showVibeInput ? (
|
|
1745
1728
|
<ActionRow
|
|
1746
1729
|
label={action === 'vibing' ? 'Starting…' : 'Vibing'}
|
|
1747
|
-
tint="#
|
|
1730
|
+
tint="#5645d8"
|
|
1748
1731
|
onPress={handleVibingButton}
|
|
1749
1732
|
disabled={busy}
|
|
1750
1733
|
busy={action === 'vibing'}
|
|
@@ -1794,31 +1777,6 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1794
1777
|
</Text>
|
|
1795
1778
|
)}
|
|
1796
1779
|
|
|
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
1780
|
</View>
|
|
1823
1781
|
|
|
1824
1782
|
{progress !== null && (
|
|
@@ -1846,9 +1804,8 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1846
1804
|
<Text style={styles.cancelBtnText}>Cancel</Text>
|
|
1847
1805
|
</Pressable>
|
|
1848
1806
|
</ScrollView>
|
|
1849
|
-
</
|
|
1850
|
-
|
|
1851
|
-
</Pressable>
|
|
1807
|
+
</View>
|
|
1808
|
+
</View>
|
|
1852
1809
|
</Modal>
|
|
1853
1810
|
)}
|
|
1854
1811
|
{runnerAuthModal ? (
|
|
@@ -1913,8 +1870,17 @@ const styles = StyleSheet.create({
|
|
|
1913
1870
|
dogfoodChoiceRow: { flexDirection: 'row', gap: 7 },
|
|
1914
1871
|
dogfoodChoice: { borderRadius: 9, borderWidth: 1, borderColor: '#d8d8e3', backgroundColor: '#fff', paddingHorizontal: 10, paddingVertical: 8 },
|
|
1915
1872
|
dogfoodChoiceSelected: { borderColor: '#818cf8', backgroundColor: 'rgba(129,140,248,0.18)' },
|
|
1873
|
+
dogfoodChoiceDisabled: { opacity: 0.48 },
|
|
1916
1874
|
dogfoodChoiceText: { color: '#666671', fontSize: 12, fontWeight: '700' },
|
|
1917
1875
|
dogfoodChoiceTextSelected: { color: '#5645d8' },
|
|
1876
|
+
dogfoodExpandedPanel: { gap: 8, borderRadius: 11, borderWidth: 1, borderColor: '#d8d8e3', backgroundColor: 'rgba(255,255,255,0.72)', padding: 10 },
|
|
1877
|
+
dogfoodProjectChoice: { gap: 3, borderRadius: 9, borderWidth: 1, borderColor: '#d8d8e3', backgroundColor: '#fff', paddingHorizontal: 10, paddingVertical: 9 },
|
|
1878
|
+
dogfoodProjectDetail: { color: '#777783', fontSize: 10, lineHeight: 14 },
|
|
1879
|
+
dogfoodStageHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 10 },
|
|
1880
|
+
dogfoodStageCopy: { flex: 1, gap: 2 },
|
|
1881
|
+
dogfoodStageTitle: { color: '#222229', fontSize: 13, fontWeight: '700' },
|
|
1882
|
+
dogfoodSmallAction: { borderRadius: 8, borderWidth: 1, borderColor: '#d8d8e3', backgroundColor: '#fff', paddingHorizontal: 10, paddingVertical: 7 },
|
|
1883
|
+
dogfoodSmallActionText: { color: '#5645d8', fontSize: 11, fontWeight: '800' },
|
|
1918
1884
|
dogfoodConsole: { maxHeight: 260, gap: 4, borderRadius: 11, padding: 10, backgroundColor: '#15151b' },
|
|
1919
1885
|
dogfoodConsoleStatus: { color: '#a5b4fc', fontSize: 12, fontWeight: '800' },
|
|
1920
1886
|
dogfoodConsoleLine: { color: '#d1d5db', fontSize: 10, lineHeight: 14, fontFamily: Platform.select({ ios: 'Menlo', android: 'monospace', default: 'monospace' }) },
|
|
@@ -1927,7 +1893,7 @@ const styles = StyleSheet.create({
|
|
|
1927
1893
|
gap: 4,
|
|
1928
1894
|
},
|
|
1929
1895
|
reloadHint: {
|
|
1930
|
-
color: '#
|
|
1896
|
+
color: '#666671',
|
|
1931
1897
|
fontSize: 11,
|
|
1932
1898
|
lineHeight: 15,
|
|
1933
1899
|
paddingHorizontal: 4,
|
|
@@ -1959,7 +1925,7 @@ const styles = StyleSheet.create({
|
|
|
1959
1925
|
backgroundColor: 'transparent',
|
|
1960
1926
|
},
|
|
1961
1927
|
vibeCancelBtnText: {
|
|
1962
|
-
color: '#
|
|
1928
|
+
color: '#5f5f69',
|
|
1963
1929
|
fontSize: 14,
|
|
1964
1930
|
fontWeight: '600',
|
|
1965
1931
|
},
|
|
@@ -1967,7 +1933,7 @@ const styles = StyleSheet.create({
|
|
|
1967
1933
|
paddingHorizontal: 16,
|
|
1968
1934
|
paddingVertical: 8,
|
|
1969
1935
|
borderRadius: 8,
|
|
1970
|
-
backgroundColor: '#
|
|
1936
|
+
backgroundColor: '#5645d8',
|
|
1971
1937
|
minWidth: 72,
|
|
1972
1938
|
alignItems: 'center',
|
|
1973
1939
|
},
|
|
@@ -1977,7 +1943,7 @@ const styles = StyleSheet.create({
|
|
|
1977
1943
|
fontWeight: '700',
|
|
1978
1944
|
},
|
|
1979
1945
|
vibeTaskLine: {
|
|
1980
|
-
color: '#
|
|
1946
|
+
color: '#5645d8',
|
|
1981
1947
|
fontSize: 12,
|
|
1982
1948
|
marginTop: -4,
|
|
1983
1949
|
fontFamily: Platform.select({ ios: 'Menlo', android: 'monospace', default: 'monospace' }),
|
|
@@ -1987,10 +1953,7 @@ const styles = StyleSheet.create({
|
|
|
1987
1953
|
backgroundColor: 'rgba(0,0,0,0.55)',
|
|
1988
1954
|
justifyContent: 'flex-end',
|
|
1989
1955
|
},
|
|
1990
|
-
|
|
1991
|
-
width: '100%',
|
|
1992
|
-
justifyContent: 'flex-end',
|
|
1993
|
-
},
|
|
1956
|
+
backdrop: StyleSheet.absoluteFillObject,
|
|
1994
1957
|
modal: {
|
|
1995
1958
|
backgroundColor: '#f8f8fb',
|
|
1996
1959
|
borderTopLeftRadius: 22,
|
|
@@ -2049,7 +2012,7 @@ const styles = StyleSheet.create({
|
|
|
2049
2012
|
tabs: { flexDirection: 'row', gap: 6, padding: 3, borderRadius: 12, backgroundColor: '#ededf3' },
|
|
2050
2013
|
tab: { flex: 1, minHeight: 36, borderRadius: 9, alignItems: 'center', justifyContent: 'center' },
|
|
2051
2014
|
tabSelected: { backgroundColor: '#fff' },
|
|
2052
|
-
tabText: { color: '#
|
|
2015
|
+
tabText: { color: '#666671', fontSize: 12, fontWeight: '700' },
|
|
2053
2016
|
tabTextSelected: { color: '#6252e8' },
|
|
2054
2017
|
tabContent: { gap: 12 },
|
|
2055
2018
|
hidden: { display: 'none' },
|
|
@@ -2074,7 +2037,7 @@ const styles = StyleSheet.create({
|
|
|
2074
2037
|
},
|
|
2075
2038
|
machineRoutes: { flexDirection: 'row', gap: 8 },
|
|
2076
2039
|
machineRouteCard: { flex: 1, minWidth: 0, borderRadius: 11, padding: 10, gap: 3, backgroundColor: '#fff', borderWidth: 1, borderColor: '#e1e1e8' },
|
|
2077
|
-
machineRouteLabel: { color: '#
|
|
2040
|
+
machineRouteLabel: { color: '#666671', fontSize: 10, fontWeight: '700', textTransform: 'uppercase' },
|
|
2078
2041
|
machineRouteValue: { color: '#222229', fontSize: 12, fontWeight: '700' },
|
|
2079
2042
|
machineHeader: {
|
|
2080
2043
|
flexDirection: 'row',
|
|
@@ -2110,7 +2073,7 @@ const styles = StyleSheet.create({
|
|
|
2110
2073
|
letterSpacing: 0.8,
|
|
2111
2074
|
},
|
|
2112
2075
|
machineAction: {
|
|
2113
|
-
color: '#
|
|
2076
|
+
color: '#5645d8',
|
|
2114
2077
|
fontSize: 12,
|
|
2115
2078
|
fontWeight: '700',
|
|
2116
2079
|
},
|
|
@@ -2141,7 +2104,7 @@ const styles = StyleSheet.create({
|
|
|
2141
2104
|
},
|
|
2142
2105
|
runnerSectionSubtitle: {
|
|
2143
2106
|
marginTop: 2,
|
|
2144
|
-
color: '#
|
|
2107
|
+
color: '#666671',
|
|
2145
2108
|
fontSize: 12,
|
|
2146
2109
|
},
|
|
2147
2110
|
runnerRefreshBtn: {
|
|
@@ -2168,15 +2131,15 @@ const styles = StyleSheet.create({
|
|
|
2168
2131
|
},
|
|
2169
2132
|
runnerCardOk: {
|
|
2170
2133
|
borderColor: 'rgba(34,197,94,0.28)',
|
|
2171
|
-
backgroundColor: 'rgba(
|
|
2134
|
+
backgroundColor: 'rgba(34,197,94,0.08)',
|
|
2172
2135
|
},
|
|
2173
2136
|
runnerCardWarning: {
|
|
2174
2137
|
borderColor: 'rgba(251,191,36,0.28)',
|
|
2175
|
-
backgroundColor: 'rgba(
|
|
2138
|
+
backgroundColor: 'rgba(245,158,11,0.08)',
|
|
2176
2139
|
},
|
|
2177
2140
|
runnerCardError: {
|
|
2178
2141
|
borderColor: 'rgba(248,113,113,0.28)',
|
|
2179
|
-
backgroundColor: 'rgba(
|
|
2142
|
+
backgroundColor: 'rgba(239,68,68,0.07)',
|
|
2180
2143
|
},
|
|
2181
2144
|
runnerCardTop: {
|
|
2182
2145
|
flexDirection: 'row',
|
|
@@ -2195,13 +2158,13 @@ const styles = StyleSheet.create({
|
|
|
2195
2158
|
color: '#666671',
|
|
2196
2159
|
},
|
|
2197
2160
|
runnerCardStatusOk: {
|
|
2198
|
-
color: '#
|
|
2161
|
+
color: '#137a3f',
|
|
2199
2162
|
},
|
|
2200
2163
|
runnerCardStatusWarning: {
|
|
2201
|
-
color: '#
|
|
2164
|
+
color: '#9a5700',
|
|
2202
2165
|
},
|
|
2203
2166
|
runnerCardStatusError: {
|
|
2204
|
-
color: '#
|
|
2167
|
+
color: '#b42318',
|
|
2205
2168
|
},
|
|
2206
2169
|
runnerCardDetail: {
|
|
2207
2170
|
color: '#858590',
|
|
@@ -2212,26 +2175,26 @@ const styles = StyleSheet.create({
|
|
|
2212
2175
|
borderRadius: 10,
|
|
2213
2176
|
borderWidth: 1,
|
|
2214
2177
|
borderColor: 'rgba(129,140,248,0.35)',
|
|
2215
|
-
backgroundColor: 'rgba(
|
|
2178
|
+
backgroundColor: 'rgba(86,69,216,0.10)',
|
|
2216
2179
|
paddingHorizontal: 12,
|
|
2217
2180
|
paddingVertical: 8,
|
|
2218
2181
|
},
|
|
2219
2182
|
runnerActionBtnText: {
|
|
2220
|
-
color: '#
|
|
2183
|
+
color: '#5645d8',
|
|
2221
2184
|
fontSize: 12,
|
|
2222
2185
|
fontWeight: '700',
|
|
2223
2186
|
},
|
|
2224
|
-
runnerActionBtnSelected: { borderColor: '#
|
|
2187
|
+
runnerActionBtnSelected: { borderColor: '#6555df', backgroundColor: 'rgba(86,69,216,0.18)' },
|
|
2225
2188
|
routingSummary: { flexDirection: 'row', alignItems: 'center', gap: 8, paddingHorizontal: 2 },
|
|
2226
2189
|
routingSummaryLabel: { color: '#7c7c87', fontSize: 11, fontWeight: '600' },
|
|
2227
2190
|
routingSummaryValue: { flex: 1, color: '#6555df', fontSize: 12, fontWeight: '700', textAlign: 'right' },
|
|
2228
2191
|
modelChoiceRow: { gap: 6, paddingTop: 2 },
|
|
2229
2192
|
modelChoice: { borderRadius: 9, borderWidth: 1, borderColor: 'rgba(148,163,184,0.18)', paddingHorizontal: 9, paddingVertical: 6 },
|
|
2230
|
-
modelChoiceSelected: { borderColor: '#
|
|
2193
|
+
modelChoiceSelected: { borderColor: '#6555df', backgroundColor: 'rgba(86,69,216,0.12)' },
|
|
2231
2194
|
modelChoiceText: { color: '#73737e', fontSize: 11 },
|
|
2232
2195
|
modelChoiceTextSelected: { color: '#5e4ce6', fontWeight: '700' },
|
|
2233
2196
|
runnerSectionError: {
|
|
2234
|
-
color: '#
|
|
2197
|
+
color: '#b42318',
|
|
2235
2198
|
fontSize: 12,
|
|
2236
2199
|
lineHeight: 18,
|
|
2237
2200
|
},
|
|
@@ -2251,13 +2214,13 @@ const styles = StyleSheet.create({
|
|
|
2251
2214
|
borderRadius: 3,
|
|
2252
2215
|
},
|
|
2253
2216
|
toast: {
|
|
2254
|
-
color: '#
|
|
2217
|
+
color: '#137a3f',
|
|
2255
2218
|
fontSize: 13,
|
|
2256
2219
|
textAlign: 'center',
|
|
2257
2220
|
marginTop: 4,
|
|
2258
2221
|
},
|
|
2259
2222
|
error: {
|
|
2260
|
-
color: '#
|
|
2223
|
+
color: '#b42318',
|
|
2261
2224
|
fontSize: 12,
|
|
2262
2225
|
textAlign: 'center',
|
|
2263
2226
|
marginTop: 4,
|