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.
Files changed (37) hide show
  1. package/app.plugin.js +24 -10
  2. package/dist/DogfoodQuickControls.js +7 -0
  3. package/dist/DogfoodRuntime.d.ts +6 -1
  4. package/dist/DogfoodRuntime.js +9 -3
  5. package/dist/DogfoodSessionUi.d.ts +1 -0
  6. package/dist/DogfoodSessionUi.js +5 -2
  7. package/dist/FeedbackModal.js +249 -278
  8. package/dist/FeedbackModalTheme.d.ts +5 -0
  9. package/dist/FeedbackModalTheme.js +29 -0
  10. package/dist/P2PClient.d.ts +2 -0
  11. package/dist/P2PClient.js +1 -1
  12. package/dist/P2PDogfoodDriver.js +17 -0
  13. package/dist/YaverFeedback.js +10 -0
  14. package/dist/__tests__/DogfoodRuntime.test.js +14 -0
  15. package/dist/__tests__/FeedbackModalContract.test.d.ts +1 -0
  16. package/dist/__tests__/FeedbackModalContract.test.js +70 -0
  17. package/dist/__tests__/NativeDogfoodShortcut.test.js +23 -0
  18. package/dist/__tests__/P2PDogfoodDriver.test.js +4 -1
  19. package/dist/__tests__/YaverFeedback.test.js +37 -12
  20. package/dist/index.d.ts +4 -5
  21. package/dist/index.js +4 -5
  22. package/ios/YaverHotReload.swift +3 -4
  23. package/package.json +1 -1
  24. package/src/DogfoodQuickControls.tsx +7 -0
  25. package/src/DogfoodRuntime.ts +21 -4
  26. package/src/DogfoodSessionUi.tsx +6 -2
  27. package/src/FeedbackModal.tsx +319 -372
  28. package/src/FeedbackModalTheme.ts +29 -0
  29. package/src/P2PClient.ts +3 -1
  30. package/src/P2PDogfoodDriver.ts +17 -0
  31. package/src/YaverFeedback.ts +10 -0
  32. package/src/__tests__/DogfoodRuntime.test.ts +15 -0
  33. package/src/__tests__/FeedbackModalContract.test.ts +80 -0
  34. package/src/__tests__/NativeDogfoodShortcut.test.ts +25 -0
  35. package/src/__tests__/P2PDogfoodDriver.test.ts +4 -0
  36. package/src/__tests__/YaverFeedback.test.ts +40 -12
  37. package/src/index.ts +4 -5
@@ -37,21 +37,25 @@ exports.FeedbackModal = void 0;
37
37
  const react_1 = __importStar(require("react"));
38
38
  const react_native_1 = require("react-native");
39
39
  const YaverFeedback_1 = require("./YaverFeedback");
40
- const capture_1 = require("./capture");
41
- const upload_1 = require("./upload");
42
- const P2PClient_1 = require("./P2PClient");
43
40
  const AuthOverlay_1 = require("./AuthOverlay");
44
41
  const QuickActionIcon_1 = require("./QuickActionIcon");
45
42
  const YaverModeBadge_1 = require("./YaverModeBadge");
46
43
  const VibeChatScreen_1 = require("./VibeChatScreen");
47
44
  const DogfoodQuickControls_1 = require("./DogfoodQuickControls");
48
- const DeployPanel_1 = require("./DeployPanel");
49
45
  const auth_1 = require("./auth");
50
46
  const reloadActions_1 = require("./reloadActions");
51
47
  const preferences_1 = require("./preferences");
52
48
  const DogfoodRuntime_1 = require("./DogfoodRuntime");
53
49
  const P2PDogfoodDriver_1 = require("./P2PDogfoodDriver");
54
50
  const DogfoodSessionUi_1 = require("./DogfoodSessionUi");
51
+ const FeedbackModalTheme_1 = require("./FeedbackModalTheme");
52
+ function dogfoodCheckoutDetail(project) {
53
+ if (!project)
54
+ return 'Choose a Git checkout on the selected machine';
55
+ return [project.path, project.branch ? `branch ${project.branch}` : project.gitRemote ? 'Git checkout' : 'checkout']
56
+ .filter(Boolean)
57
+ .join(' · ');
58
+ }
55
59
  const PRIMARY_RUNNER_IDS = ['claude', 'codex', 'opencode'];
56
60
  function normalizeRunnerStatusRows(rows) {
57
61
  const byId = new Map();
@@ -174,17 +178,12 @@ const FeedbackModal = () => {
174
178
  // Which reload action is in flight, so only that button spins.
175
179
  const [reloadingId, setReloadingId] = (0, react_1.useState)(null);
176
180
  const [runnerAuthModal, setRunnerAuthModal] = (0, react_1.useState)(null);
177
- // Vibing-input mode: same expand-on-tap pattern as email login.
178
- // Tap "Vibing" once the button reveals an input + Send; that lets
179
- // the user say WHAT they want to vibe on instead of firing a canned
180
- // "pick something for me" prompt (which in 0.7.13 pointed Claude at
181
- // the wrong project because the matcher grepped the prompt itself).
181
+ // Signed-in users get the composer immediately. Signed-out users still get
182
+ // an explicit entry action so auth/setup failures remain named and visible.
182
183
  const [showVibeInput, setShowVibeInput] = (0, react_1.useState)(false);
183
- const [showDeploy, setShowDeploy] = (0, react_1.useState)(false);
184
184
  const [vibePrompt, setVibePrompt] = (0, react_1.useState)('');
185
185
  const [lastVibeTaskId, setLastVibeTaskId] = (0, react_1.useState)(null);
186
186
  const [quickIconColorPreset, setQuickIconColorPreset] = (0, react_1.useState)(null);
187
- const [keyboardInset, setKeyboardInset] = (0, react_1.useState)(0);
188
187
  const [machineCard, setMachineCard] = (0, react_1.useState)({
189
188
  device: null,
190
189
  reachable: null,
@@ -206,6 +205,11 @@ const FeedbackModal = () => {
206
205
  const [dogfoodProject, setDogfoodProject] = (0, react_1.useState)(null);
207
206
  const [dogfoodLane, setDogfoodLane] = (0, react_1.useState)('browser');
208
207
  const [dogfoodNativeAvailable, setDogfoodNativeAvailable] = (0, react_1.useState)(false);
208
+ const [dogfoodBrowserAvailable, setDogfoodBrowserAvailable] = (0, react_1.useState)(false);
209
+ const [dogfoodNativeTargets, setDogfoodNativeTargets] = (0, react_1.useState)([]);
210
+ const [dogfoodNativeTargetId, setDogfoodNativeTargetId] = (0, react_1.useState)('');
211
+ const [dogfoodSetupStage, setDogfoodSetupStage] = (0, react_1.useState)('setup');
212
+ const [dogfoodExpandedStep, setDogfoodExpandedStep] = (0, react_1.useState)(null);
209
213
  const [dogfoodRuntime, setDogfoodRuntime] = (0, react_1.useState)(null);
210
214
  const [dogfoodSetupLoading, setDogfoodSetupLoading] = (0, react_1.useState)(false);
211
215
  const dogfoodControllerRef = (0, react_1.useRef)(null);
@@ -241,13 +245,25 @@ const FeedbackModal = () => {
241
245
  if (preferred) {
242
246
  const framework = preferred.framework || onboarding.framework || 'expo';
243
247
  const capabilities = await client.getDogfoodRemoteRuntimeCapabilities(preferred.path, framework).catch(() => null);
244
- const nativeRuntimeAvailable = !!capabilities?.targets.some((target) => target.enabled && target.id !== 'browser-window');
245
- if (mountedRef.current)
248
+ const targets = capabilities?.targets || [];
249
+ const nativeTargets = targets.filter((target) => target.id !== 'browser-window');
250
+ const enabledNativeTargets = nativeTargets.filter((target) => target.enabled);
251
+ const nativeRuntimeAvailable = enabledNativeTargets.length > 0;
252
+ const browserRuntimeAvailable = targets.some((target) => target.enabled && target.id === 'browser-window');
253
+ if (mountedRef.current) {
246
254
  setDogfoodNativeAvailable(nativeRuntimeAvailable);
255
+ setDogfoodBrowserAvailable(browserRuntimeAvailable);
256
+ setDogfoodNativeTargets(nativeTargets);
257
+ setDogfoodNativeTargetId((current) => enabledNativeTargets.some((target) => target.id === current)
258
+ ? current
259
+ : enabledNativeTargets[0]?.id || '');
260
+ }
247
261
  const savedLane = await (0, preferences_1.getPreferredDogfoodLane)(onboarding.appId);
248
- const savedSupported = (0, DogfoodRuntime_1.dogfoodLaneOptions)(framework, { nativeRuntimeAvailable })
262
+ const savedSupported = (0, DogfoodRuntime_1.dogfoodLaneOptions)(framework, { nativeRuntimeAvailable, browserRuntimeAvailable })
249
263
  .some((option) => option.lane === savedLane && option.supported);
250
- setDogfoodLane(savedLane && savedSupported ? savedLane : (0, DogfoodRuntime_1.defaultDogfoodLane)(framework));
264
+ setDogfoodLane(savedLane && savedSupported
265
+ ? savedLane
266
+ : (0, DogfoodRuntime_1.defaultDogfoodLane)(framework, { nativeRuntimeAvailable, browserRuntimeAvailable }));
251
267
  }
252
268
  }
253
269
  catch (cause) {
@@ -262,14 +278,33 @@ const FeedbackModal = () => {
262
278
  (0, react_1.useEffect)(() => {
263
279
  let cancelled = false;
264
280
  setDogfoodNativeAvailable(false);
281
+ setDogfoodBrowserAvailable(false);
282
+ setDogfoodNativeTargets([]);
283
+ setDogfoodNativeTargetId('');
265
284
  const client = YaverFeedback_1.YaverFeedback.getP2PClient();
266
285
  if (!client || !dogfoodProject)
267
286
  return () => { cancelled = true; };
268
287
  const framework = dogfoodProject.framework || YaverFeedback_1.YaverFeedback.getDogfoodOnboarding()?.framework || 'expo';
269
288
  void client.getDogfoodRemoteRuntimeCapabilities(dogfoodProject.path, framework)
270
289
  .then((value) => {
271
- if (!cancelled)
272
- setDogfoodNativeAvailable(value.targets.some((target) => target.enabled && target.id !== 'browser-window'));
290
+ if (cancelled)
291
+ return;
292
+ const nativeTargets = value.targets.filter((target) => target.id !== 'browser-window');
293
+ const enabledNativeTargets = nativeTargets.filter((target) => target.enabled);
294
+ setDogfoodNativeAvailable(enabledNativeTargets.length > 0);
295
+ setDogfoodBrowserAvailable(value.targets.some((target) => target.enabled && target.id === 'browser-window'));
296
+ setDogfoodNativeTargets(nativeTargets);
297
+ setDogfoodNativeTargetId((current) => enabledNativeTargets.some((target) => target.id === current)
298
+ ? current
299
+ : enabledNativeTargets[0]?.id || '');
300
+ const laneCapabilities = {
301
+ nativeRuntimeAvailable: enabledNativeTargets.length > 0,
302
+ browserRuntimeAvailable: value.targets.some((target) => target.enabled && target.id === 'browser-window'),
303
+ };
304
+ setDogfoodLane((current) => (0, DogfoodRuntime_1.dogfoodLaneOptions)(framework, laneCapabilities)
305
+ .some((option) => option.lane === current && option.supported)
306
+ ? current
307
+ : (0, DogfoodRuntime_1.defaultDogfoodLane)(framework, laneCapabilities));
273
308
  })
274
309
  .catch(() => { });
275
310
  return () => { cancelled = true; };
@@ -286,14 +321,16 @@ const FeedbackModal = () => {
286
321
  workDir: dogfoodProject.path,
287
322
  framework,
288
323
  lane: dogfoodLane,
324
+ nativeTargetId: dogfoodLane === 'webrtc' ? dogfoodNativeTargetId : undefined,
289
325
  }, (0, P2PDogfoodDriver_1.createP2PDogfoodDriver)(client), {
290
326
  onChange: (snapshot) => { if (mountedRef.current)
291
327
  setDogfoodRuntime(snapshot); },
292
328
  });
293
329
  dogfoodControllerRef.current = controller;
294
330
  setDogfoodRuntime(controller.snapshot());
331
+ setDogfoodSetupStage('runtime');
295
332
  await controller.trigger().catch(() => { });
296
- }, [dogfoodLane, dogfoodProject]);
333
+ }, [dogfoodLane, dogfoodNativeTargetId, dogfoodProject]);
297
334
  /**
298
335
  * Ask the machine what its dev server is doing, so the reload actions can
299
336
  * be enabled/disabled against reality rather than against a guess.
@@ -486,16 +523,20 @@ const FeedbackModal = () => {
486
523
  // user's feedback preference merely to open Developer Mode.
487
524
  if (YaverFeedback_1.YaverFeedback.isEnabled() || onboarding) {
488
525
  const directDogfood = YaverFeedback_1.YaverFeedback.getDogfoodStatus().active;
526
+ const authenticated = YaverFeedback_1.YaverFeedback.isAuthed();
489
527
  setDogfoodActive(directDogfood);
490
528
  setVisible(true);
491
529
  setError(null);
492
530
  setToast(null);
493
531
  setProgress(null);
494
532
  setAction('idle');
495
- setShowVibeInput(directDogfood);
533
+ setShowVibeInput(authenticated || directDogfood);
496
534
  setVibePrompt('');
497
535
  if (onboarding) {
498
- setActiveTab('settings');
536
+ setActiveTab(authenticated ? 'chat' : 'settings');
537
+ setDogfoodSetupStage('setup');
538
+ setDogfoodExpandedStep(null);
539
+ setDogfoodRuntime(null);
499
540
  void loadDogfoodOnboarding();
500
541
  }
501
542
  // Re-read the "user hid the quick icon" flag on every open so
@@ -566,30 +607,14 @@ const FeedbackModal = () => {
566
607
  }, 5000);
567
608
  return () => clearInterval(interval);
568
609
  }, [loadRunnerStatuses, loadSelectedMachine, refreshDevSnapshot, visible]);
569
- (0, react_1.useEffect)(() => {
570
- if (!visible) {
571
- setKeyboardInset(0);
572
- return;
573
- }
574
- const showEvent = react_native_1.Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow';
575
- const hideEvent = react_native_1.Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide';
576
- const showSub = react_native_1.Keyboard.addListener(showEvent, (event) => {
577
- setKeyboardInset(event.endCoordinates?.height ?? 0);
578
- });
579
- const hideSub = react_native_1.Keyboard.addListener(hideEvent, () => {
580
- setKeyboardInset(0);
581
- });
582
- return () => {
583
- showSub.remove();
584
- hideSub.remove();
585
- };
586
- }, [visible]);
587
610
  (0, react_1.useEffect)(() => {
588
611
  if (!visible || !showVibeInput)
589
612
  return;
590
- const timer = setTimeout(() => scrollRef.current?.scrollToEnd({ animated: true }), keyboardInset > 0 ? 120 : 40);
613
+ // UIKit owns keyboard insets on the ScrollView. This one bounded scroll only
614
+ // reveals the newly mounted composer; it does not add a second inset.
615
+ const timer = setTimeout(() => scrollRef.current?.scrollToEnd({ animated: true }), 80);
591
616
  return () => clearTimeout(timer);
592
- }, [keyboardInset, showVibeInput, visible]);
617
+ }, [showVibeInput, visible]);
593
618
  const closeSoon = (0, react_1.useCallback)((delayMs = 1200) => {
594
619
  setTimeout(() => {
595
620
  if (mountedRef.current)
@@ -605,6 +630,9 @@ const FeedbackModal = () => {
605
630
  setShowVibeInput(false);
606
631
  setVibePrompt('');
607
632
  setRunnerStatusError(null);
633
+ setDogfoodSetupStage('setup');
634
+ setDogfoodExpandedStep(null);
635
+ setDogfoodRuntime(null);
608
636
  void dogfoodControllerRef.current?.stop().catch(() => { });
609
637
  dogfoodControllerRef.current = null;
610
638
  YaverFeedback_1.YaverFeedback.clearDogfoodOnboarding();
@@ -791,107 +819,7 @@ const FeedbackModal = () => {
791
819
  setAction('idle');
792
820
  }
793
821
  }, [closeSoon, loadSelectedMachine, runWithReconnect]);
794
- const uploadBundleWithOptionalFix = (0, react_1.useCallback)(async (bundle, fixOnUpload, successToast, failureToast) => {
795
- const client = YaverFeedback_1.YaverFeedback.getP2PClient();
796
- const config = YaverFeedback_1.YaverFeedback.getConfig();
797
- if (!client || !config?.agentUrl) {
798
- setError('Not connected to the agent yet.');
799
- return;
800
- }
801
- try {
802
- const uploaded = await (0, upload_1.uploadFeedback)(config.agentUrl, config.authToken ?? '', bundle, YaverFeedback_1.YaverFeedback.getRelayPassword());
803
- // The agent returns the new report id as `id` (see
804
- // feedback_http.go::ReceiveFeedback). Trigger the fix loop if we got
805
- // one back; otherwise just ack the upload.
806
- const reportId = uploaded?.id ??
807
- uploaded?.reportId;
808
- if (reportId && fixOnUpload) {
809
- try {
810
- await client.triggerFix(reportId);
811
- setToast(successToast);
812
- }
813
- catch (err) {
814
- setToast(failureToast ?? 'Report uploaded — fix trigger failed');
815
- setError(err instanceof Error ? err.message : String(err));
816
- }
817
- }
818
- else {
819
- setToast(successToast);
820
- }
821
- closeSoon(1400);
822
- }
823
- catch (err) {
824
- setError(err instanceof Error ? err.message : String(err));
825
- }
826
- }, [closeSoon]);
827
- const handleScreenshotAndFix = (0, react_1.useCallback)(async () => {
828
- setAction('capturing');
829
- setError(null);
830
- setVisible(false);
831
- await new Promise((resolve) => setTimeout(resolve, 350));
832
- let path;
833
- try {
834
- path = await (0, capture_1.captureScreenshot)();
835
- }
836
- catch (err) {
837
- setVisible(true);
838
- setError(err instanceof Error ? err.message : String(err));
839
- setAction('idle');
840
- return;
841
- }
842
- setVisible(true);
843
- await new Promise((resolve) => setTimeout(resolve, 150));
844
- try {
845
- const { Dimensions } = require('react-native');
846
- const { width, height } = Dimensions.get('window');
847
- const cfg = YaverFeedback_1.YaverFeedback.getConfig();
848
- const identity = (0, P2PClient_1.resolveReportIdentity)({
849
- projectName: cfg?.projectName,
850
- bundleId: cfg?.bundleId,
851
- surface: cfg?.surface,
852
- surfaces: cfg?.surfaces,
853
- stack: cfg?.stack,
854
- stacks: cfg?.stacks,
855
- testSurfaces: cfg?.testSurfaces,
856
- feedbackSdk: cfg?.feedbackSdk,
857
- feedbackTransport: cfg?.feedbackTransport,
858
- voiceCapabilities: cfg?.voiceCapabilities,
859
- sttProvider: cfg?.sttProvider,
860
- ttsProvider: cfg?.ttsProvider,
861
- });
862
- const deviceInfo = {
863
- platform: react_native_1.Platform.OS,
864
- osVersion: String(react_native_1.Platform.Version),
865
- model: react_native_1.Platform.OS === 'ios' ? 'iOS Device' : 'Android Device',
866
- screenWidth: width,
867
- screenHeight: height,
868
- appName: identity.appName,
869
- };
870
- const capturedErrors = YaverFeedback_1.YaverFeedback.getCapturedErrors();
871
- const bundle = {
872
- metadata: {
873
- timestamp: new Date().toISOString(),
874
- deviceInfo,
875
- app: identity.app,
876
- project: identity.project,
877
- userNote: '[Screenshot + Fix]',
878
- },
879
- screenshots: [path],
880
- errors: capturedErrors.length > 0 ? capturedErrors : undefined,
881
- };
882
- await uploadBundleWithOptionalFix(bundle, true, 'Fix task started');
883
- }
884
- finally {
885
- if (mountedRef.current)
886
- setAction('idle');
887
- }
888
- }, [uploadBundleWithOptionalFix]);
889
- /*
890
- const handleFileUpload = useCallback(async () => {
891
- ...
892
- }, [uploadBundleWithOptionalFix]);
893
- */
894
- // ─── 3. Vibing ─────────────────────────────────────────────────────
822
+ // ─── Chat ──────────────────────────────────────────────────────────
895
823
  // First tap expands the input; second submit fires the actual
896
824
  // /vibing/execute. Mirrors the Yaver mobile app's Vibing tab —
897
825
  // user types what they want, hits Send, sees the task id back. If
@@ -1025,42 +953,54 @@ const FeedbackModal = () => {
1025
953
  && (selectedDogfoodRunner.ready || selectedDogfoodRunner.authConfigured);
1026
954
  const dogfoodModelReady = !selectedDogfoodRunner?.models?.length
1027
955
  || !!preferredModel && selectedDogfoodRunner.models.some((model) => model.id === preferredModel);
1028
- const dogfoodLaneChoices = (0, DogfoodRuntime_1.dogfoodLaneOptions)(dogfoodProject?.framework || YaverFeedback_1.YaverFeedback.getDogfoodOnboarding()?.framework || 'expo', { nativeRuntimeAvailable: dogfoodNativeAvailable });
1029
- const dogfoodLaneReady = dogfoodLaneChoices.some((option) => option.lane === dogfoodLane && option.supported);
1030
- const dogfoodReadinessSteps = [
1031
- {
1032
- key: 'oauth', label: 'Yaver OAuth',
1033
- detail: YaverFeedback_1.YaverFeedback.isAuthed() ? 'Signed in · session saved' : 'Sign in required',
1034
- tone: YaverFeedback_1.YaverFeedback.isAuthed() ? 'ready' : 'attention',
1035
- },
1036
- {
1037
- key: 'machine', label: 'Remote PC',
1038
- detail: machineCard.device ? machineCard.title : 'Choose a reachable development machine',
1039
- tone: machineCard.device && machineCard.status === 'live' ? 'ready' : 'attention',
1040
- },
1041
- {
1042
- key: 'installation', label: 'This installation',
1043
- detail: dogfoodEnrollment?.status === 'active' ? 'Device key approved' : dogfoodEnrollment?.status || 'Checking device key',
1044
- tone: dogfoodEnrollment?.status === 'active' ? 'ready'
1045
- : dogfoodEnrollment?.status === 'failed' ? 'blocked' : 'pending',
1046
- },
956
+ const dogfoodFramework = dogfoodProject?.framework || YaverFeedback_1.YaverFeedback.getDogfoodOnboarding()?.framework || 'expo';
957
+ const dogfoodLaneChoices = (0, DogfoodRuntime_1.dogfoodLaneOptions)(dogfoodFramework, {
958
+ nativeRuntimeAvailable: dogfoodNativeAvailable,
959
+ browserRuntimeAvailable: dogfoodBrowserAvailable,
960
+ });
961
+ const selectedDogfoodNativeTarget = dogfoodNativeTargets.find((target) => target.id === dogfoodNativeTargetId) || null;
962
+ const dogfoodLaneReady = dogfoodLaneChoices.some((option) => option.lane === dogfoodLane && option.supported)
963
+ && (dogfoodLane !== 'webrtc' || !!selectedDogfoodNativeTarget?.enabled);
964
+ const dogfoodMachineReady = !!machineCard.device && machineCard.status === 'live';
965
+ const dogfoodSetupReady = dogfoodMachineReady && !!dogfoodProject && dogfoodRunnerReady && dogfoodModelReady;
966
+ const dogfoodSetupSteps = [
1047
967
  {
1048
- key: 'runner', label: 'Runner',
1049
- detail: dogfoodRunnerReady ? selectedDogfoodRunner?.name || preferredRunner || 'Ready' : 'Choose or configure a coding runner',
1050
- tone: dogfoodRunnerReady ? 'ready' : 'attention',
968
+ key: 'box',
969
+ label: 'Remote box',
970
+ detail: dogfoodMachineReady ? machineCard.title : 'Choose a reachable development machine',
971
+ tone: dogfoodMachineReady ? 'ready' : 'attention',
972
+ actionLabel: machineCard.device ? 'Change' : 'Pick',
973
+ onAction: () => YaverFeedback_1.YaverFeedback.showMachinePicker(),
1051
974
  },
1052
975
  {
1053
- key: 'model', label: 'Model',
1054
- detail: dogfoodModelReady ? preferredModel || 'Runner default' : 'Choose a model',
1055
- tone: dogfoodModelReady ? 'ready' : 'attention',
976
+ key: 'runner',
977
+ label: 'Runner',
978
+ detail: dogfoodRunnerReady
979
+ ? [selectedDogfoodRunner?.name || preferredRunner || 'Ready', preferredModel || 'default model'].join(' · ')
980
+ : 'Choose or configure a coding runner',
981
+ tone: dogfoodRunnerReady && dogfoodModelReady ? 'ready' : 'attention',
982
+ actionLabel: dogfoodExpandedStep === 'runner' ? 'Done' : dogfoodRunnerReady ? 'Change' : 'Choose',
983
+ expanded: dogfoodExpandedStep === 'runner',
984
+ onAction: () => setDogfoodExpandedStep((current) => current === 'runner' ? null : 'runner'),
1056
985
  },
1057
986
  {
1058
- key: 'lane', label: 'Runtime lane',
1059
- detail: dogfoodLaneChoices.find((option) => option.lane === dogfoodLane)?.label || dogfoodLane,
1060
- tone: dogfoodLaneReady ? 'ready' : 'blocked',
987
+ key: 'checkout',
988
+ label: 'Checkout',
989
+ detail: dogfoodCheckoutDetail(dogfoodProject),
990
+ tone: dogfoodProject ? 'ready' : 'attention',
991
+ actionLabel: dogfoodExpandedStep === 'checkout' ? 'Done' : dogfoodProject ? 'Change' : 'Choose',
992
+ expanded: dogfoodExpandedStep === 'checkout',
993
+ onAction: () => setDogfoodExpandedStep((current) => current === 'checkout' ? null : 'checkout'),
1061
994
  },
1062
995
  ];
1063
- const dogfoodStartBlocked = !dogfoodProject || !dogfoodRunnerReady || !dogfoodModelReady || !dogfoodLaneReady;
996
+ const dogfoodStartBlocked = !dogfoodSetupReady || !dogfoodLaneReady;
997
+ const dogfoodSourceLabel = dogfoodLane === 'webrtc'
998
+ ? selectedDogfoodNativeTarget
999
+ ? [selectedDogfoodNativeTarget.label, selectedDogfoodNativeTarget.platform].filter(Boolean).join(' · ')
1000
+ : 'Native simulator, emulator, or device'
1001
+ : dogfoodLane === 'hermes'
1002
+ ? `Hermes build · ${machineCard.title}`
1003
+ : `${dogfoodFramework === 'flutter' ? 'Flutter web compiler' : 'Metro / browser build'} · ${machineCard.title}`;
1064
1004
  // Once the user fires off a vibe task, swap the entire modal body
1065
1005
  // for the live chat screen. The chat manages its own SSE
1066
1006
  // subscription, multi-turn follow-ups, and Reload button. Closing
@@ -1091,9 +1031,9 @@ const FeedbackModal = () => {
1091
1031
  <QuickActionIcon_1.QuickActionIcon />
1092
1032
  <YaverModeBadgeGate />
1093
1033
  {visible && (<react_native_1.Modal visible={visible} animationType="slide" transparent onRequestClose={handleClose}>
1094
- <react_native_1.Pressable style={styles.overlay} onPress={handleClose}>
1095
- <react_native_1.KeyboardAvoidingView behavior={react_native_1.Platform.OS === 'ios' ? 'padding' : 'height'} keyboardVerticalOffset={react_native_1.Platform.OS === 'ios' ? 12 : 0} style={styles.kbAvoider} pointerEvents="box-none">
1096
- <react_native_1.Pressable
1034
+ <react_native_1.View style={styles.overlay}>
1035
+ <react_native_1.Pressable style={styles.backdrop} onPress={handleClose} accessibilityLabel="Close feedback"/>
1036
+ <react_native_1.View
1097
1037
  // Tablet: cap modal width and center as a card-style
1098
1038
  // sheet rather than a phone bottom sheet that stretches
1099
1039
  // across a 12.9" iPad. Phone behaviour unchanged.
@@ -1108,16 +1048,8 @@ const FeedbackModal = () => {
1108
1048
  borderTopRightRadius: 22,
1109
1049
  }
1110
1050
  : null,
1111
- ]} onPress={(e) => {
1112
- e.stopPropagation();
1113
- react_native_1.Keyboard.dismiss();
1114
- }}>
1115
- <react_native_1.ScrollView ref={scrollRef} style={styles.scroll} contentContainerStyle={[
1116
- styles.scrollContent,
1117
- showVibeInput && keyboardInset > 0
1118
- ? { paddingBottom: 8 + keyboardInset }
1119
- : null,
1120
- ]} keyboardShouldPersistTaps="handled" keyboardDismissMode={react_native_1.Platform.OS === 'ios' ? 'interactive' : 'on-drag'} contentInsetAdjustmentBehavior="always" automaticallyAdjustKeyboardInsets={react_native_1.Platform.OS === 'ios'}>
1051
+ ]}>
1052
+ <react_native_1.ScrollView ref={scrollRef} style={styles.scroll} contentContainerStyle={styles.scrollContent} keyboardShouldPersistTaps="handled" keyboardDismissMode={react_native_1.Platform.OS === 'ios' ? 'interactive' : 'on-drag'} contentInsetAdjustmentBehavior="always" automaticallyAdjustKeyboardInsets={react_native_1.Platform.OS === 'ios'}>
1121
1053
  <react_native_1.View style={styles.header}>
1122
1054
  <react_native_1.Text style={styles.title}>
1123
1055
  {dogfoodActive ? `${YaverFeedback_1.YaverFeedback.getDogfoodStatus().label || 'App'} Developer Mode` : 'Send Feedback'}
@@ -1138,10 +1070,12 @@ const FeedbackModal = () => {
1138
1070
  {YaverFeedback_1.YaverFeedback.getDogfoodOnboarding() ? (<react_native_1.View style={styles.dogfoodWizard}>
1139
1071
  <react_native_1.Text style={styles.dogfoodWizardTitle}>Dogfood this app</react_native_1.Text>
1140
1072
  <react_native_1.Text style={styles.dogfoodWizardHint}>
1141
- OAuth ✓ · machine {machineCard.device ? '✓' : 'required'} · installation {dogfoodEnrollment?.status || 'checking'}
1073
+ {dogfoodEnrollment?.status === 'active'
1074
+ ? 'Signed in · this installation is approved'
1075
+ : `Signed in · installation ${dogfoodEnrollment?.status || 'checking'}`}
1142
1076
  </react_native_1.Text>
1143
- <DogfoodSessionUi_1.DogfoodStatusRail steps={dogfoodReadinessSteps}/>
1144
- {dogfoodEnrollment?.installationId ? (<react_native_1.Text selectable style={styles.dogfoodInstallationId}>
1077
+ {dogfoodEnrollment?.status === 'active' && dogfoodSetupStage === 'setup' ? (<DogfoodSessionUi_1.DogfoodStatusRail steps={dogfoodSetupSteps} colors={FeedbackModalTheme_1.FEEDBACK_DOGFOOD_LIGHT_COLORS}/>) : null}
1078
+ {dogfoodEnrollment?.status !== 'active' && dogfoodEnrollment?.installationId ? (<react_native_1.Text selectable style={styles.dogfoodInstallationId}>
1145
1079
  This device · {dogfoodEnrollment.installationId}
1146
1080
  </react_native_1.Text>) : null}
1147
1081
  {dogfoodEnrollment?.status !== 'active' ? (<react_native_1.View style={styles.dogfoodPendingBox}>
@@ -1153,53 +1087,99 @@ const FeedbackModal = () => {
1153
1087
  <react_native_1.Text style={styles.runnerRefreshBtnText}>{dogfoodSetupLoading ? 'Checking…' : 'Check approval'}</react_native_1.Text>
1154
1088
  </react_native_1.Pressable>
1155
1089
  </react_native_1.View>) : (<>
1156
- <react_native_1.Text style={styles.dogfoodStepLabel}>Project on selected machine</react_native_1.Text>
1157
- <react_native_1.ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.dogfoodChoiceRow}>
1158
- {dogfoodProjects.map((project) => (<react_native_1.Pressable key={project.path} onPress={() => {
1159
- setDogfoodProject(project);
1160
- setDogfoodLane((0, DogfoodRuntime_1.defaultDogfoodLane)(project.framework || YaverFeedback_1.YaverFeedback.getDogfoodOnboarding()?.framework || 'expo'));
1161
- setDogfoodRuntime(null);
1162
- }} style={[styles.dogfoodChoice, dogfoodProject?.path === project.path && styles.dogfoodChoiceSelected]}>
1163
- <react_native_1.Text style={[styles.dogfoodChoiceText, dogfoodProject?.path === project.path && styles.dogfoodChoiceTextSelected]}>{project.name}</react_native_1.Text>
1164
- </react_native_1.Pressable>))}
1165
- </react_native_1.ScrollView>
1166
- <react_native_1.Text style={styles.dogfoodStepLabel}>Coding agent</react_native_1.Text>
1167
- <react_native_1.View style={styles.dogfoodChoiceRow}>
1168
- {runnerCards.filter((row) => row.ready || row.authConfigured).map((row) => (<react_native_1.Pressable key={row.id} onPress={() => {
1169
- const nextModel = row.models?.find((model) => model.isDefault)?.id || row.models?.[0]?.id || '';
1170
- setPreferredRunnerState(row.id);
1171
- setPreferredModelState(nextModel);
1172
- void (0, preferences_1.setPreferredRunner)(row.id);
1173
- void (0, preferences_1.setPreferredModel)(nextModel || null);
1174
- }} style={[styles.dogfoodChoice, preferredRunner === row.id && styles.dogfoodChoiceSelected]} accessibilityRole="button" accessibilityState={{ selected: preferredRunner === row.id }} accessibilityLabel={`Use ${row.name} for Dogfood`}>
1175
- <react_native_1.Text style={[styles.dogfoodChoiceText, preferredRunner === row.id && styles.dogfoodChoiceTextSelected]}>{row.name}</react_native_1.Text>
1176
- </react_native_1.Pressable>))}
1177
- </react_native_1.View>
1178
- {readyRunnerCount === 0 ? (<react_native_1.Text style={styles.dogfoodWizardHint}>Sign in or configure a coding agent under Coding Agents below.</react_native_1.Text>) : null}
1179
- {selectedDogfoodRunner?.models?.length ? (<>
1180
- <react_native_1.Text style={styles.dogfoodStepLabel}>Model</react_native_1.Text>
1181
- <react_native_1.ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.dogfoodChoiceRow}>
1182
- {selectedDogfoodRunner.models.map((model) => (<react_native_1.Pressable key={model.id} onPress={() => {
1183
- setPreferredModelState(model.id);
1184
- void (0, preferences_1.setPreferredModel)(model.id);
1185
- }} style={[styles.dogfoodChoice, preferredModel === model.id && styles.dogfoodChoiceSelected]} accessibilityRole="button" accessibilityState={{ selected: preferredModel === model.id }} accessibilityLabel={`Use ${model.name || model.id} model for Dogfood`}>
1186
- <react_native_1.Text style={[styles.dogfoodChoiceText, preferredModel === model.id && styles.dogfoodChoiceTextSelected]}>{model.name || model.id}</react_native_1.Text>
1187
- </react_native_1.Pressable>))}
1188
- </react_native_1.ScrollView>
1090
+ {dogfoodSetupStage === 'setup' ? (<>
1091
+ {dogfoodExpandedStep === 'runner' ? (<react_native_1.View style={styles.dogfoodExpandedPanel}>
1092
+ <react_native_1.Text style={styles.dogfoodStepLabel}>Coding runner</react_native_1.Text>
1093
+ <react_native_1.View style={styles.dogfoodChoiceRow}>
1094
+ {runnerCards.filter((row) => row.ready || row.authConfigured).map((row) => (<react_native_1.Pressable key={row.id} onPress={() => {
1095
+ const nextModel = row.models?.find((model) => model.isDefault)?.id || row.models?.[0]?.id || '';
1096
+ setPreferredRunnerState(row.id);
1097
+ setPreferredModelState(nextModel);
1098
+ void (0, preferences_1.setPreferredRunner)(row.id);
1099
+ void (0, preferences_1.setPreferredModel)(nextModel || null);
1100
+ }} style={[styles.dogfoodChoice, preferredRunner === row.id && styles.dogfoodChoiceSelected]} accessibilityRole="button" accessibilityState={{ selected: preferredRunner === row.id }}>
1101
+ <react_native_1.Text style={[styles.dogfoodChoiceText, preferredRunner === row.id && styles.dogfoodChoiceTextSelected]}>{row.name}</react_native_1.Text>
1102
+ </react_native_1.Pressable>))}
1103
+ </react_native_1.View>
1104
+ {readyRunnerCount === 0 ? (<react_native_1.Text style={styles.dogfoodWizardHint}>Configure a coding runner below, then retry.</react_native_1.Text>) : null}
1105
+ {selectedDogfoodRunner?.models?.length ? (<>
1106
+ <react_native_1.Text style={styles.dogfoodStepLabel}>Model</react_native_1.Text>
1107
+ <react_native_1.View style={styles.dogfoodChoiceRow}>
1108
+ {selectedDogfoodRunner.models.map((model) => (<react_native_1.Pressable key={model.id} onPress={() => {
1109
+ setPreferredModelState(model.id);
1110
+ void (0, preferences_1.setPreferredModel)(model.id);
1111
+ }} style={[styles.dogfoodChoice, preferredModel === model.id && styles.dogfoodChoiceSelected]}>
1112
+ <react_native_1.Text style={[styles.dogfoodChoiceText, preferredModel === model.id && styles.dogfoodChoiceTextSelected]}>{model.name || model.id}</react_native_1.Text>
1113
+ </react_native_1.Pressable>))}
1114
+ </react_native_1.View>
1115
+ </>) : null}
1116
+ </react_native_1.View>) : null}
1117
+ {dogfoodExpandedStep === 'checkout' ? (<react_native_1.View style={styles.dogfoodExpandedPanel}>
1118
+ <react_native_1.Text style={styles.dogfoodStepLabel}>Git checkout on remote box</react_native_1.Text>
1119
+ {dogfoodProjects.map((project) => (<react_native_1.Pressable key={project.path} onPress={() => {
1120
+ setDogfoodProject(project);
1121
+ setDogfoodLane((0, DogfoodRuntime_1.defaultDogfoodLane)(project.framework || YaverFeedback_1.YaverFeedback.getDogfoodOnboarding()?.framework || 'expo'));
1122
+ setDogfoodRuntime(null);
1123
+ }} style={[styles.dogfoodProjectChoice, dogfoodProject?.path === project.path && styles.dogfoodChoiceSelected]}>
1124
+ <react_native_1.Text style={[styles.dogfoodChoiceText, dogfoodProject?.path === project.path && styles.dogfoodChoiceTextSelected]}>{project.name}</react_native_1.Text>
1125
+ <react_native_1.Text style={styles.dogfoodProjectDetail}>{dogfoodCheckoutDetail(project)}</react_native_1.Text>
1126
+ </react_native_1.Pressable>))}
1127
+ </react_native_1.View>) : null}
1128
+ <ActionRow label="Choose runtime" tint="#5645d8" onPress={() => {
1129
+ setDogfoodExpandedStep(null);
1130
+ setDogfoodSetupStage('lane');
1131
+ }} disabled={!dogfoodSetupReady}/>
1189
1132
  </>) : null}
1190
- <react_native_1.Text style={styles.dogfoodStepLabel}>Runtime lane</react_native_1.Text>
1191
- <DogfoodSessionUi_1.DogfoodLanePicker options={dogfoodLaneChoices} selected={dogfoodLane} onSelect={(lane) => {
1192
- setDogfoodLane(lane);
1193
- const appId = YaverFeedback_1.YaverFeedback.getDogfoodOnboarding()?.appId;
1194
- if (appId)
1195
- void (0, preferences_1.setPreferredDogfoodLane)(appId, lane);
1196
- }}/>
1197
- <react_native_1.Text style={styles.dogfoodWizardHint}>
1198
- {[preferredRunner || 'Choose a coding agent', preferredModel].filter(Boolean).join(' · ')}
1199
- </react_native_1.Text>
1200
- <ActionRow label={dogfoodRuntime && !['idle', 'ready', 'failed', 'stopped'].includes(dogfoodRuntime.phase) ? dogfoodRuntime.message : 'Start Dogfood'} tint="#818cf8" onPress={() => void startDogfoodRuntime()} disabled={dogfoodStartBlocked || !!(dogfoodRuntime && !['idle', 'ready', 'failed', 'stopped'].includes(dogfoodRuntime.phase))} busy={!!dogfoodRuntime && !['idle', 'ready', 'failed', 'stopped'].includes(dogfoodRuntime.phase)}/>
1201
- {dogfoodRuntime ? (<>
1202
- <DogfoodSessionUi_1.DogfoodLiveConsole lane={dogfoodRuntime.project.lane} phase={dogfoodRuntime.phase} message={dogfoodRuntime.message} logs={dogfoodRuntime.logs} failure={dogfoodRuntime.failure}/>
1133
+
1134
+ {dogfoodSetupStage === 'lane' ? (<>
1135
+ <react_native_1.View style={styles.dogfoodStageHeader}>
1136
+ <react_native_1.View style={styles.dogfoodStageCopy}>
1137
+ <react_native_1.Text style={styles.dogfoodStepLabel}>Runtime</react_native_1.Text>
1138
+ <react_native_1.Text style={styles.dogfoodStageTitle}>{dogfoodProject?.name} · {dogfoodFramework}</react_native_1.Text>
1139
+ </react_native_1.View>
1140
+ <react_native_1.Pressable onPress={() => setDogfoodSetupStage('setup')} style={styles.dogfoodSmallAction}>
1141
+ <react_native_1.Text style={styles.dogfoodSmallActionText}>Back</react_native_1.Text>
1142
+ </react_native_1.Pressable>
1143
+ </react_native_1.View>
1144
+ <DogfoodSessionUi_1.DogfoodLanePicker options={dogfoodLaneChoices} selected={dogfoodLane} colors={FeedbackModalTheme_1.FEEDBACK_DOGFOOD_LIGHT_COLORS} onSelect={(lane) => {
1145
+ setDogfoodLane(lane);
1146
+ const appId = YaverFeedback_1.YaverFeedback.getDogfoodOnboarding()?.appId;
1147
+ if (appId)
1148
+ void (0, preferences_1.setPreferredDogfoodLane)(appId, lane);
1149
+ }}/>
1150
+ {dogfoodLane === 'webrtc' ? (<react_native_1.View style={styles.dogfoodExpandedPanel}>
1151
+ <react_native_1.Text style={styles.dogfoodStepLabel}>Simulator, emulator, or device</react_native_1.Text>
1152
+ {dogfoodNativeTargets.map((target) => (<react_native_1.Pressable key={target.id} disabled={!target.enabled} onPress={() => setDogfoodNativeTargetId(target.id)} style={[
1153
+ styles.dogfoodProjectChoice,
1154
+ dogfoodNativeTargetId === target.id && styles.dogfoodChoiceSelected,
1155
+ !target.enabled && styles.dogfoodChoiceDisabled,
1156
+ ]}>
1157
+ <react_native_1.Text style={[styles.dogfoodChoiceText, dogfoodNativeTargetId === target.id && styles.dogfoodChoiceTextSelected]}>{target.label}</react_native_1.Text>
1158
+ <react_native_1.Text style={styles.dogfoodProjectDetail}>{target.enabled
1159
+ ? [target.platform, target.displaySurface || target.surface, 'WebRTC'].filter(Boolean).join(' · ')
1160
+ : target.reason || 'Unavailable on this box'}</react_native_1.Text>
1161
+ </react_native_1.Pressable>))}
1162
+ </react_native_1.View>) : null}
1163
+ <react_native_1.Text style={styles.dogfoodWizardHint}>Logs will be labelled with their real source: remote browser build, Hermes host, iOS Simulator, Android emulator, or connected device.</react_native_1.Text>
1164
+ <ActionRow label="Start Dogfood" tint="#5645d8" onPress={() => void startDogfoodRuntime()} disabled={dogfoodStartBlocked}/>
1165
+ </>) : null}
1166
+
1167
+ {dogfoodSetupStage === 'runtime' && dogfoodRuntime ? (<>
1168
+ <react_native_1.View style={styles.dogfoodStageHeader}>
1169
+ <react_native_1.View style={styles.dogfoodStageCopy}>
1170
+ <react_native_1.Text style={styles.dogfoodStepLabel}>Dogfooding</react_native_1.Text>
1171
+ <react_native_1.Text style={styles.dogfoodStageTitle}>{dogfoodSourceLabel}</react_native_1.Text>
1172
+ </react_native_1.View>
1173
+ <react_native_1.Pressable onPress={() => {
1174
+ void dogfoodControllerRef.current?.stop().catch(() => { });
1175
+ dogfoodControllerRef.current = null;
1176
+ setDogfoodRuntime(null);
1177
+ setDogfoodSetupStage('lane');
1178
+ }} style={styles.dogfoodSmallAction}>
1179
+ <react_native_1.Text style={styles.dogfoodSmallActionText}>Change</react_native_1.Text>
1180
+ </react_native_1.Pressable>
1181
+ </react_native_1.View>
1182
+ <DogfoodSessionUi_1.DogfoodLiveConsole lane={dogfoodRuntime.project.lane} sourceLabel={dogfoodSourceLabel} phase={dogfoodRuntime.phase} message={dogfoodRuntime.message} logs={dogfoodRuntime.logs} failure={dogfoodRuntime.failure} colors={FeedbackModalTheme_1.FEEDBACK_DOGFOOD_CONSOLE_COLORS}/>
1203
1183
  {dogfoodRuntime.result?.url ? (<react_native_1.Pressable onPress={() => void react_native_1.Linking.openURL(dogfoodRuntime.result.url)} style={styles.dogfoodOpenPreview}>
1204
1184
  <react_native_1.Text style={styles.dogfoodOpenPreviewText}>Open dogfooded app</react_native_1.Text>
1205
1185
  </react_native_1.Pressable>) : null}
@@ -1392,7 +1372,7 @@ const FeedbackModal = () => {
1392
1372
  {availableReloadActions.map((reloadAction) => (<react_native_1.View key={reloadAction.id} style={styles.reloadRow}>
1393
1373
  <ActionRow label={reloadingId === reloadAction.id
1394
1374
  ? `${reloadAction.label}…`
1395
- : reloadAction.label} tint={reloadAction.id === 'rebuild' ? '#38bdf8' : '#fbbf24'} onPress={() => {
1375
+ : reloadAction.label} tint={reloadAction.id === 'rebuild' ? '#0369a1' : '#9a5700'} onPress={() => {
1396
1376
  void handleReloadAction(reloadAction);
1397
1377
  }}
1398
1378
  // Never `disabled` at the Pressable level for a blocked
@@ -1409,13 +1389,9 @@ const FeedbackModal = () => {
1409
1389
  </react_native_1.View>
1410
1390
 
1411
1391
  <react_native_1.View style={[styles.tabContent, activeTab !== 'chat' && styles.hidden]}>
1412
- {/* 3. Vibing expands to an input box on first tap
1413
- so the user says WHAT they want to vibe on, just
1414
- like the Yaver mobile app's Vibing tab. Second
1415
- tap (Send) fires /vibing/execute with the typed
1416
- prompt + resolved bundle id so the agent routes
1417
- to the right repo. */}
1418
- {!showVibeInput ? (<ActionRow label={action === 'vibing' ? 'Starting…' : 'Vibing'} tint="#818cf8" onPress={handleVibingButton} disabled={busy} busy={action === 'vibing'}/>) : (<react_native_1.View style={styles.vibeInputRow}>
1392
+ {/* Chat creates the first task, then VibeChatScreen owns the
1393
+ transcript and every MCP-backed follow-up. */}
1394
+ {!showVibeInput ? (<ActionRow label={action === 'vibing' ? 'Starting…' : 'Vibing'} tint="#5645d8" onPress={handleVibingButton} disabled={busy} busy={action === 'vibing'}/>) : (<react_native_1.View style={styles.vibeInputRow}>
1419
1395
  <react_native_1.TextInput style={styles.vibeInput} placeholder="What do you want to vibe on?" placeholderTextColor="#666" value={vibePrompt} onChangeText={setVibePrompt} multiline autoFocus editable={action !== 'vibing'} blurOnSubmit={false}/>
1420
1396
  <react_native_1.View style={styles.vibeInputButtons}>
1421
1397
  <react_native_1.Pressable onPress={() => { setShowVibeInput(false); setVibePrompt(''); }} style={({ pressed }) => [styles.vibeCancelBtn, pressed && styles.buttonPressed]} disabled={action === 'vibing'}>
@@ -1434,16 +1410,6 @@ const FeedbackModal = () => {
1434
1410
  Last vibing task: {lastVibeTaskId.slice(0, 12)}…
1435
1411
  </react_native_1.Text>)}
1436
1412
 
1437
- {/* Screenshot & Fix */}
1438
- {!dogfoodActive ? <ActionRow label={action === 'capturing' ? 'Working…' : 'Screenshot & Fix'} tint="#22c55e" onPress={handleScreenshotAndFix} disabled={busy} busy={action === 'capturing'}/> : null}
1439
-
1440
- {/* Deploy — opens an inline panel that talks to
1441
- /fleet/deploy-options on the agent and lets the user
1442
- pick TestFlight / Play / Both, then a machine to run
1443
- it on. Capabilities (e.g. "Linux can't TestFlight")
1444
- come from the agent's doctor probes — no client-side
1445
- platform smarts here. */}
1446
- {!dogfoodActive && (!showDeploy ? (<ActionRow label="Deploy" tint="#7f8cf7" onPress={() => setShowDeploy(true)} disabled={busy}/>) : (<DeployPanel_1.DeployPanel onClose={() => setShowDeploy(false)}/>))}
1447
1413
  </react_native_1.View>
1448
1414
 
1449
1415
  {progress !== null && (<react_native_1.View style={styles.progressTrack}>
@@ -1462,9 +1428,8 @@ const FeedbackModal = () => {
1462
1428
  <react_native_1.Text style={styles.cancelBtnText}>Cancel</react_native_1.Text>
1463
1429
  </react_native_1.Pressable>
1464
1430
  </react_native_1.ScrollView>
1465
- </react_native_1.Pressable>
1466
- </react_native_1.KeyboardAvoidingView>
1467
- </react_native_1.Pressable>
1431
+ </react_native_1.View>
1432
+ </react_native_1.View>
1468
1433
  </react_native_1.Modal>)}
1469
1434
  {runnerAuthModal ? (<RunnerAuthNativeModal runner={runnerAuthModal} onClose={() => {
1470
1435
  setRunnerAuthModal(null);
@@ -1495,8 +1460,17 @@ const styles = react_native_1.StyleSheet.create({
1495
1460
  dogfoodChoiceRow: { flexDirection: 'row', gap: 7 },
1496
1461
  dogfoodChoice: { borderRadius: 9, borderWidth: 1, borderColor: '#d8d8e3', backgroundColor: '#fff', paddingHorizontal: 10, paddingVertical: 8 },
1497
1462
  dogfoodChoiceSelected: { borderColor: '#818cf8', backgroundColor: 'rgba(129,140,248,0.18)' },
1463
+ dogfoodChoiceDisabled: { opacity: 0.48 },
1498
1464
  dogfoodChoiceText: { color: '#666671', fontSize: 12, fontWeight: '700' },
1499
1465
  dogfoodChoiceTextSelected: { color: '#5645d8' },
1466
+ dogfoodExpandedPanel: { gap: 8, borderRadius: 11, borderWidth: 1, borderColor: '#d8d8e3', backgroundColor: 'rgba(255,255,255,0.72)', padding: 10 },
1467
+ dogfoodProjectChoice: { gap: 3, borderRadius: 9, borderWidth: 1, borderColor: '#d8d8e3', backgroundColor: '#fff', paddingHorizontal: 10, paddingVertical: 9 },
1468
+ dogfoodProjectDetail: { color: '#777783', fontSize: 10, lineHeight: 14 },
1469
+ dogfoodStageHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 10 },
1470
+ dogfoodStageCopy: { flex: 1, gap: 2 },
1471
+ dogfoodStageTitle: { color: '#222229', fontSize: 13, fontWeight: '700' },
1472
+ dogfoodSmallAction: { borderRadius: 8, borderWidth: 1, borderColor: '#d8d8e3', backgroundColor: '#fff', paddingHorizontal: 10, paddingVertical: 7 },
1473
+ dogfoodSmallActionText: { color: '#5645d8', fontSize: 11, fontWeight: '800' },
1500
1474
  dogfoodConsole: { maxHeight: 260, gap: 4, borderRadius: 11, padding: 10, backgroundColor: '#15151b' },
1501
1475
  dogfoodConsoleStatus: { color: '#a5b4fc', fontSize: 12, fontWeight: '800' },
1502
1476
  dogfoodConsoleLine: { color: '#d1d5db', fontSize: 10, lineHeight: 14, fontFamily: react_native_1.Platform.select({ ios: 'Menlo', android: 'monospace', default: 'monospace' }) },
@@ -1509,7 +1483,7 @@ const styles = react_native_1.StyleSheet.create({
1509
1483
  gap: 4,
1510
1484
  },
1511
1485
  reloadHint: {
1512
- color: '#8b8b93',
1486
+ color: '#666671',
1513
1487
  fontSize: 11,
1514
1488
  lineHeight: 15,
1515
1489
  paddingHorizontal: 4,
@@ -1541,7 +1515,7 @@ const styles = react_native_1.StyleSheet.create({
1541
1515
  backgroundColor: 'transparent',
1542
1516
  },
1543
1517
  vibeCancelBtnText: {
1544
- color: '#999',
1518
+ color: '#5f5f69',
1545
1519
  fontSize: 14,
1546
1520
  fontWeight: '600',
1547
1521
  },
@@ -1549,7 +1523,7 @@ const styles = react_native_1.StyleSheet.create({
1549
1523
  paddingHorizontal: 16,
1550
1524
  paddingVertical: 8,
1551
1525
  borderRadius: 8,
1552
- backgroundColor: '#818cf8',
1526
+ backgroundColor: '#5645d8',
1553
1527
  minWidth: 72,
1554
1528
  alignItems: 'center',
1555
1529
  },
@@ -1559,7 +1533,7 @@ const styles = react_native_1.StyleSheet.create({
1559
1533
  fontWeight: '700',
1560
1534
  },
1561
1535
  vibeTaskLine: {
1562
- color: '#818cf8',
1536
+ color: '#5645d8',
1563
1537
  fontSize: 12,
1564
1538
  marginTop: -4,
1565
1539
  fontFamily: react_native_1.Platform.select({ ios: 'Menlo', android: 'monospace', default: 'monospace' }),
@@ -1569,10 +1543,7 @@ const styles = react_native_1.StyleSheet.create({
1569
1543
  backgroundColor: 'rgba(0,0,0,0.55)',
1570
1544
  justifyContent: 'flex-end',
1571
1545
  },
1572
- kbAvoider: {
1573
- width: '100%',
1574
- justifyContent: 'flex-end',
1575
- },
1546
+ backdrop: react_native_1.StyleSheet.absoluteFillObject,
1576
1547
  modal: {
1577
1548
  backgroundColor: '#f8f8fb',
1578
1549
  borderTopLeftRadius: 22,
@@ -1631,7 +1602,7 @@ const styles = react_native_1.StyleSheet.create({
1631
1602
  tabs: { flexDirection: 'row', gap: 6, padding: 3, borderRadius: 12, backgroundColor: '#ededf3' },
1632
1603
  tab: { flex: 1, minHeight: 36, borderRadius: 9, alignItems: 'center', justifyContent: 'center' },
1633
1604
  tabSelected: { backgroundColor: '#fff' },
1634
- tabText: { color: '#858590', fontSize: 12, fontWeight: '700' },
1605
+ tabText: { color: '#666671', fontSize: 12, fontWeight: '700' },
1635
1606
  tabTextSelected: { color: '#6252e8' },
1636
1607
  tabContent: { gap: 12 },
1637
1608
  hidden: { display: 'none' },
@@ -1656,7 +1627,7 @@ const styles = react_native_1.StyleSheet.create({
1656
1627
  },
1657
1628
  machineRoutes: { flexDirection: 'row', gap: 8 },
1658
1629
  machineRouteCard: { flex: 1, minWidth: 0, borderRadius: 11, padding: 10, gap: 3, backgroundColor: '#fff', borderWidth: 1, borderColor: '#e1e1e8' },
1659
- machineRouteLabel: { color: '#7b7b86', fontSize: 10, fontWeight: '700', textTransform: 'uppercase' },
1630
+ machineRouteLabel: { color: '#666671', fontSize: 10, fontWeight: '700', textTransform: 'uppercase' },
1660
1631
  machineRouteValue: { color: '#222229', fontSize: 12, fontWeight: '700' },
1661
1632
  machineHeader: {
1662
1633
  flexDirection: 'row',
@@ -1692,7 +1663,7 @@ const styles = react_native_1.StyleSheet.create({
1692
1663
  letterSpacing: 0.8,
1693
1664
  },
1694
1665
  machineAction: {
1695
- color: '#a5b4fc',
1666
+ color: '#5645d8',
1696
1667
  fontSize: 12,
1697
1668
  fontWeight: '700',
1698
1669
  },
@@ -1723,7 +1694,7 @@ const styles = react_native_1.StyleSheet.create({
1723
1694
  },
1724
1695
  runnerSectionSubtitle: {
1725
1696
  marginTop: 2,
1726
- color: '#83838e',
1697
+ color: '#666671',
1727
1698
  fontSize: 12,
1728
1699
  },
1729
1700
  runnerRefreshBtn: {
@@ -1750,15 +1721,15 @@ const styles = react_native_1.StyleSheet.create({
1750
1721
  },
1751
1722
  runnerCardOk: {
1752
1723
  borderColor: 'rgba(34,197,94,0.28)',
1753
- backgroundColor: 'rgba(20,83,45,0.20)',
1724
+ backgroundColor: 'rgba(34,197,94,0.08)',
1754
1725
  },
1755
1726
  runnerCardWarning: {
1756
1727
  borderColor: 'rgba(251,191,36,0.28)',
1757
- backgroundColor: 'rgba(120,53,15,0.18)',
1728
+ backgroundColor: 'rgba(245,158,11,0.08)',
1758
1729
  },
1759
1730
  runnerCardError: {
1760
1731
  borderColor: 'rgba(248,113,113,0.28)',
1761
- backgroundColor: 'rgba(127,29,29,0.18)',
1732
+ backgroundColor: 'rgba(239,68,68,0.07)',
1762
1733
  },
1763
1734
  runnerCardTop: {
1764
1735
  flexDirection: 'row',
@@ -1777,13 +1748,13 @@ const styles = react_native_1.StyleSheet.create({
1777
1748
  color: '#666671',
1778
1749
  },
1779
1750
  runnerCardStatusOk: {
1780
- color: '#86efac',
1751
+ color: '#137a3f',
1781
1752
  },
1782
1753
  runnerCardStatusWarning: {
1783
- color: '#fcd34d',
1754
+ color: '#9a5700',
1784
1755
  },
1785
1756
  runnerCardStatusError: {
1786
- color: '#fca5a5',
1757
+ color: '#b42318',
1787
1758
  },
1788
1759
  runnerCardDetail: {
1789
1760
  color: '#858590',
@@ -1794,26 +1765,26 @@ const styles = react_native_1.StyleSheet.create({
1794
1765
  borderRadius: 10,
1795
1766
  borderWidth: 1,
1796
1767
  borderColor: 'rgba(129,140,248,0.35)',
1797
- backgroundColor: 'rgba(67,56,202,0.22)',
1768
+ backgroundColor: 'rgba(86,69,216,0.10)',
1798
1769
  paddingHorizontal: 12,
1799
1770
  paddingVertical: 8,
1800
1771
  },
1801
1772
  runnerActionBtnText: {
1802
- color: '#c7d2fe',
1773
+ color: '#5645d8',
1803
1774
  fontSize: 12,
1804
1775
  fontWeight: '700',
1805
1776
  },
1806
- runnerActionBtnSelected: { borderColor: '#818cf8', backgroundColor: 'rgba(79,70,229,0.44)' },
1777
+ runnerActionBtnSelected: { borderColor: '#6555df', backgroundColor: 'rgba(86,69,216,0.18)' },
1807
1778
  routingSummary: { flexDirection: 'row', alignItems: 'center', gap: 8, paddingHorizontal: 2 },
1808
1779
  routingSummaryLabel: { color: '#7c7c87', fontSize: 11, fontWeight: '600' },
1809
1780
  routingSummaryValue: { flex: 1, color: '#6555df', fontSize: 12, fontWeight: '700', textAlign: 'right' },
1810
1781
  modelChoiceRow: { gap: 6, paddingTop: 2 },
1811
1782
  modelChoice: { borderRadius: 9, borderWidth: 1, borderColor: 'rgba(148,163,184,0.18)', paddingHorizontal: 9, paddingVertical: 6 },
1812
- modelChoiceSelected: { borderColor: '#818cf8', backgroundColor: 'rgba(79,70,229,0.30)' },
1783
+ modelChoiceSelected: { borderColor: '#6555df', backgroundColor: 'rgba(86,69,216,0.12)' },
1813
1784
  modelChoiceText: { color: '#73737e', fontSize: 11 },
1814
1785
  modelChoiceTextSelected: { color: '#5e4ce6', fontWeight: '700' },
1815
1786
  runnerSectionError: {
1816
- color: '#fca5a5',
1787
+ color: '#b42318',
1817
1788
  fontSize: 12,
1818
1789
  lineHeight: 18,
1819
1790
  },
@@ -1833,13 +1804,13 @@ const styles = react_native_1.StyleSheet.create({
1833
1804
  borderRadius: 3,
1834
1805
  },
1835
1806
  toast: {
1836
- color: '#22c55e',
1807
+ color: '#137a3f',
1837
1808
  fontSize: 13,
1838
1809
  textAlign: 'center',
1839
1810
  marginTop: 4,
1840
1811
  },
1841
1812
  error: {
1842
- color: '#ef4444',
1813
+ color: '#b42318',
1843
1814
  fontSize: 12,
1844
1815
  textAlign: 'center',
1845
1816
  marginTop: 4,