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.
@@ -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; };
@@ -281,19 +316,26 @@ const FeedbackModal = () => {
281
316
  return;
282
317
  await dogfoodControllerRef.current?.stop().catch(() => { });
283
318
  const framework = dogfoodProject.framework || onboarding.framework || 'expo';
319
+ const lanePlan = (0, DogfoodRuntime_1.dogfoodLanePlan)(framework, {
320
+ nativeRuntimeAvailable: dogfoodNativeAvailable,
321
+ browserRuntimeAvailable: dogfoodBrowserAvailable,
322
+ }, dogfoodLane);
284
323
  const controller = new DogfoodRuntime_1.DogfoodController({
285
324
  name: dogfoodProject.name,
286
325
  workDir: dogfoodProject.path,
287
326
  framework,
288
- lane: dogfoodLane,
327
+ lane: lanePlan.preferred,
328
+ fallbackLane: lanePlan.fallback,
329
+ nativeTargetId: lanePlan.preferred === 'webrtc' ? dogfoodNativeTargetId : undefined,
289
330
  }, (0, P2PDogfoodDriver_1.createP2PDogfoodDriver)(client), {
290
331
  onChange: (snapshot) => { if (mountedRef.current)
291
332
  setDogfoodRuntime(snapshot); },
292
333
  });
293
334
  dogfoodControllerRef.current = controller;
294
335
  setDogfoodRuntime(controller.snapshot());
336
+ setDogfoodSetupStage('runtime');
295
337
  await controller.trigger().catch(() => { });
296
- }, [dogfoodLane, dogfoodProject]);
338
+ }, [dogfoodLane, dogfoodNativeTargetId, dogfoodProject]);
297
339
  /**
298
340
  * Ask the machine what its dev server is doing, so the reload actions can
299
341
  * be enabled/disabled against reality rather than against a guess.
@@ -486,16 +528,24 @@ const FeedbackModal = () => {
486
528
  // user's feedback preference merely to open Developer Mode.
487
529
  if (YaverFeedback_1.YaverFeedback.isEnabled() || onboarding) {
488
530
  const directDogfood = YaverFeedback_1.YaverFeedback.getDogfoodStatus().active;
531
+ const authenticated = YaverFeedback_1.YaverFeedback.isAuthed();
489
532
  setDogfoodActive(directDogfood);
490
533
  setVisible(true);
491
534
  setError(null);
492
535
  setToast(null);
493
536
  setProgress(null);
494
537
  setAction('idle');
495
- setShowVibeInput(directDogfood);
538
+ setShowVibeInput(authenticated || directDogfood);
496
539
  setVibePrompt('');
497
540
  if (onboarding) {
541
+ // A Dogfood shortcut is an explicit setup/runtime intent. Opening on
542
+ // Chat hid the machine/runner/checkout gate for signed-in SFMG users;
543
+ // keep the SDK-owned Dogfood surface visible, then show its live logs
544
+ // immediately when Start is tapped.
498
545
  setActiveTab('settings');
546
+ setDogfoodSetupStage('setup');
547
+ setDogfoodExpandedStep(null);
548
+ setDogfoodRuntime(null);
499
549
  void loadDogfoodOnboarding();
500
550
  }
501
551
  // Re-read the "user hid the quick icon" flag on every open so
@@ -566,30 +616,14 @@ const FeedbackModal = () => {
566
616
  }, 5000);
567
617
  return () => clearInterval(interval);
568
618
  }, [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
619
  (0, react_1.useEffect)(() => {
588
620
  if (!visible || !showVibeInput)
589
621
  return;
590
- const timer = setTimeout(() => scrollRef.current?.scrollToEnd({ animated: true }), keyboardInset > 0 ? 120 : 40);
622
+ // UIKit owns keyboard insets on the ScrollView. This one bounded scroll only
623
+ // reveals the newly mounted composer; it does not add a second inset.
624
+ const timer = setTimeout(() => scrollRef.current?.scrollToEnd({ animated: true }), 80);
591
625
  return () => clearTimeout(timer);
592
- }, [keyboardInset, showVibeInput, visible]);
626
+ }, [showVibeInput, visible]);
593
627
  const closeSoon = (0, react_1.useCallback)((delayMs = 1200) => {
594
628
  setTimeout(() => {
595
629
  if (mountedRef.current)
@@ -605,6 +639,9 @@ const FeedbackModal = () => {
605
639
  setShowVibeInput(false);
606
640
  setVibePrompt('');
607
641
  setRunnerStatusError(null);
642
+ setDogfoodSetupStage('setup');
643
+ setDogfoodExpandedStep(null);
644
+ setDogfoodRuntime(null);
608
645
  void dogfoodControllerRef.current?.stop().catch(() => { });
609
646
  dogfoodControllerRef.current = null;
610
647
  YaverFeedback_1.YaverFeedback.clearDogfoodOnboarding();
@@ -791,107 +828,7 @@ const FeedbackModal = () => {
791
828
  setAction('idle');
792
829
  }
793
830
  }, [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 ─────────────────────────────────────────────────────
831
+ // ─── Chat ──────────────────────────────────────────────────────────
895
832
  // First tap expands the input; second submit fires the actual
896
833
  // /vibing/execute. Mirrors the Yaver mobile app's Vibing tab —
897
834
  // user types what they want, hits Send, sees the task id back. If
@@ -1025,42 +962,59 @@ const FeedbackModal = () => {
1025
962
  && (selectedDogfoodRunner.ready || selectedDogfoodRunner.authConfigured);
1026
963
  const dogfoodModelReady = !selectedDogfoodRunner?.models?.length
1027
964
  || !!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
- },
965
+ const dogfoodFramework = dogfoodProject?.framework || YaverFeedback_1.YaverFeedback.getDogfoodOnboarding()?.framework || 'expo';
966
+ const dogfoodLaneChoices = (0, DogfoodRuntime_1.dogfoodLaneOptions)(dogfoodFramework, {
967
+ nativeRuntimeAvailable: dogfoodNativeAvailable,
968
+ browserRuntimeAvailable: dogfoodBrowserAvailable,
969
+ });
970
+ const dogfoodLanePolicy = (0, DogfoodRuntime_1.dogfoodLanePlan)(dogfoodFramework, {
971
+ nativeRuntimeAvailable: dogfoodNativeAvailable,
972
+ browserRuntimeAvailable: dogfoodBrowserAvailable,
973
+ }, dogfoodLane);
974
+ const selectedDogfoodNativeTarget = dogfoodNativeTargets.find((target) => target.id === dogfoodNativeTargetId) || null;
975
+ const dogfoodLaneReady = dogfoodLaneChoices.some((option) => option.lane === dogfoodLane && option.supported)
976
+ && (dogfoodLane !== 'webrtc' || !!selectedDogfoodNativeTarget?.enabled);
977
+ const dogfoodMachineReady = !!machineCard.device && machineCard.status === 'live';
978
+ const dogfoodSetupReady = dogfoodMachineReady && !!dogfoodProject && dogfoodRunnerReady && dogfoodModelReady;
979
+ const dogfoodSetupSteps = [
1047
980
  {
1048
- key: 'runner', label: 'Runner',
1049
- detail: dogfoodRunnerReady ? selectedDogfoodRunner?.name || preferredRunner || 'Ready' : 'Choose or configure a coding runner',
1050
- tone: dogfoodRunnerReady ? 'ready' : 'attention',
981
+ key: 'box',
982
+ label: 'Remote box',
983
+ detail: dogfoodMachineReady ? machineCard.title : 'Choose a reachable development machine',
984
+ tone: dogfoodMachineReady ? 'ready' : 'attention',
985
+ actionLabel: machineCard.device ? 'Change' : 'Pick',
986
+ onAction: () => YaverFeedback_1.YaverFeedback.showMachinePicker(),
1051
987
  },
1052
988
  {
1053
- key: 'model', label: 'Model',
1054
- detail: dogfoodModelReady ? preferredModel || 'Runner default' : 'Choose a model',
1055
- tone: dogfoodModelReady ? 'ready' : 'attention',
989
+ key: 'runner',
990
+ label: 'Runner',
991
+ detail: dogfoodRunnerReady
992
+ ? [selectedDogfoodRunner?.name || preferredRunner || 'Ready', preferredModel || 'default model'].join(' · ')
993
+ : 'Choose or configure a coding runner',
994
+ tone: dogfoodRunnerReady && dogfoodModelReady ? 'ready' : 'attention',
995
+ actionLabel: dogfoodExpandedStep === 'runner' ? 'Done' : dogfoodRunnerReady ? 'Change' : 'Choose',
996
+ expanded: dogfoodExpandedStep === 'runner',
997
+ onAction: () => setDogfoodExpandedStep((current) => current === 'runner' ? null : 'runner'),
1056
998
  },
1057
999
  {
1058
- key: 'lane', label: 'Runtime lane',
1059
- detail: dogfoodLaneChoices.find((option) => option.lane === dogfoodLane)?.label || dogfoodLane,
1060
- tone: dogfoodLaneReady ? 'ready' : 'blocked',
1000
+ key: 'checkout',
1001
+ label: 'Checkout',
1002
+ detail: dogfoodCheckoutDetail(dogfoodProject),
1003
+ tone: dogfoodProject ? 'ready' : 'attention',
1004
+ actionLabel: dogfoodExpandedStep === 'checkout' ? 'Done' : dogfoodProject ? 'Change' : 'Choose',
1005
+ expanded: dogfoodExpandedStep === 'checkout',
1006
+ onAction: () => setDogfoodExpandedStep((current) => current === 'checkout' ? null : 'checkout'),
1061
1007
  },
1062
1008
  ];
1063
- const dogfoodStartBlocked = !dogfoodProject || !dogfoodRunnerReady || !dogfoodModelReady || !dogfoodLaneReady;
1009
+ const dogfoodStartBlocked = !dogfoodSetupReady || !dogfoodLaneReady;
1010
+ const activeDogfoodLane = dogfoodRuntime?.project.lane || dogfoodLane;
1011
+ const dogfoodSourceLabel = activeDogfoodLane === 'webrtc'
1012
+ ? selectedDogfoodNativeTarget
1013
+ ? [selectedDogfoodNativeTarget.label, selectedDogfoodNativeTarget.platform].filter(Boolean).join(' · ')
1014
+ : 'Native simulator, emulator, or device'
1015
+ : activeDogfoodLane === 'hermes'
1016
+ ? `Hermes build · ${machineCard.title}`
1017
+ : `${dogfoodFramework === 'flutter' ? 'Flutter web compiler' : 'Metro / browser build'} · ${machineCard.title}`;
1064
1018
  // Once the user fires off a vibe task, swap the entire modal body
1065
1019
  // for the live chat screen. The chat manages its own SSE
1066
1020
  // subscription, multi-turn follow-ups, and Reload button. Closing
@@ -1091,9 +1045,9 @@ const FeedbackModal = () => {
1091
1045
  <QuickActionIcon_1.QuickActionIcon />
1092
1046
  <YaverModeBadgeGate />
1093
1047
  {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
1048
+ <react_native_1.View style={styles.overlay}>
1049
+ <react_native_1.Pressable style={styles.backdrop} onPress={handleClose} accessibilityLabel="Close feedback"/>
1050
+ <react_native_1.View
1097
1051
  // Tablet: cap modal width and center as a card-style
1098
1052
  // sheet rather than a phone bottom sheet that stretches
1099
1053
  // across a 12.9" iPad. Phone behaviour unchanged.
@@ -1108,16 +1062,8 @@ const FeedbackModal = () => {
1108
1062
  borderTopRightRadius: 22,
1109
1063
  }
1110
1064
  : 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'}>
1065
+ ]}>
1066
+ <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
1067
  <react_native_1.View style={styles.header}>
1122
1068
  <react_native_1.Text style={styles.title}>
1123
1069
  {dogfoodActive ? `${YaverFeedback_1.YaverFeedback.getDogfoodStatus().label || 'App'} Developer Mode` : 'Send Feedback'}
@@ -1138,10 +1084,12 @@ const FeedbackModal = () => {
1138
1084
  {YaverFeedback_1.YaverFeedback.getDogfoodOnboarding() ? (<react_native_1.View style={styles.dogfoodWizard}>
1139
1085
  <react_native_1.Text style={styles.dogfoodWizardTitle}>Dogfood this app</react_native_1.Text>
1140
1086
  <react_native_1.Text style={styles.dogfoodWizardHint}>
1141
- OAuth ✓ · machine {machineCard.device ? '✓' : 'required'} · installation {dogfoodEnrollment?.status || 'checking'}
1087
+ {dogfoodEnrollment?.status === 'active'
1088
+ ? 'Signed in · this installation is approved'
1089
+ : `Signed in · installation ${dogfoodEnrollment?.status || 'checking'}`}
1142
1090
  </react_native_1.Text>
1143
- <DogfoodSessionUi_1.DogfoodStatusRail steps={dogfoodReadinessSteps}/>
1144
- {dogfoodEnrollment?.installationId ? (<react_native_1.Text selectable style={styles.dogfoodInstallationId}>
1091
+ {dogfoodEnrollment?.status === 'active' && dogfoodSetupStage === 'setup' ? (<DogfoodSessionUi_1.DogfoodStatusRail steps={dogfoodSetupSteps} colors={FeedbackModalTheme_1.FEEDBACK_DOGFOOD_LIGHT_COLORS}/>) : null}
1092
+ {dogfoodEnrollment?.status !== 'active' && dogfoodEnrollment?.installationId ? (<react_native_1.Text selectable style={styles.dogfoodInstallationId}>
1145
1093
  This device · {dogfoodEnrollment.installationId}
1146
1094
  </react_native_1.Text>) : null}
1147
1095
  {dogfoodEnrollment?.status !== 'active' ? (<react_native_1.View style={styles.dogfoodPendingBox}>
@@ -1153,53 +1101,99 @@ const FeedbackModal = () => {
1153
1101
  <react_native_1.Text style={styles.runnerRefreshBtnText}>{dogfoodSetupLoading ? 'Checking…' : 'Check approval'}</react_native_1.Text>
1154
1102
  </react_native_1.Pressable>
1155
1103
  </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>
1104
+ {dogfoodSetupStage === 'setup' ? (<>
1105
+ {dogfoodExpandedStep === 'runner' ? (<react_native_1.View style={styles.dogfoodExpandedPanel}>
1106
+ <react_native_1.Text style={styles.dogfoodStepLabel}>Coding runner</react_native_1.Text>
1107
+ <react_native_1.View style={styles.dogfoodChoiceRow}>
1108
+ {runnerCards.filter((row) => row.ready || row.authConfigured).map((row) => (<react_native_1.Pressable key={row.id} onPress={() => {
1109
+ const nextModel = row.models?.find((model) => model.isDefault)?.id || row.models?.[0]?.id || '';
1110
+ setPreferredRunnerState(row.id);
1111
+ setPreferredModelState(nextModel);
1112
+ void (0, preferences_1.setPreferredRunner)(row.id);
1113
+ void (0, preferences_1.setPreferredModel)(nextModel || null);
1114
+ }} style={[styles.dogfoodChoice, preferredRunner === row.id && styles.dogfoodChoiceSelected]} accessibilityRole="button" accessibilityState={{ selected: preferredRunner === row.id }}>
1115
+ <react_native_1.Text style={[styles.dogfoodChoiceText, preferredRunner === row.id && styles.dogfoodChoiceTextSelected]}>{row.name}</react_native_1.Text>
1116
+ </react_native_1.Pressable>))}
1117
+ </react_native_1.View>
1118
+ {readyRunnerCount === 0 ? (<react_native_1.Text style={styles.dogfoodWizardHint}>Configure a coding runner below, then retry.</react_native_1.Text>) : null}
1119
+ {selectedDogfoodRunner?.models?.length ? (<>
1120
+ <react_native_1.Text style={styles.dogfoodStepLabel}>Model</react_native_1.Text>
1121
+ <react_native_1.View style={styles.dogfoodChoiceRow}>
1122
+ {selectedDogfoodRunner.models.map((model) => (<react_native_1.Pressable key={model.id} onPress={() => {
1123
+ setPreferredModelState(model.id);
1124
+ void (0, preferences_1.setPreferredModel)(model.id);
1125
+ }} style={[styles.dogfoodChoice, preferredModel === model.id && styles.dogfoodChoiceSelected]}>
1126
+ <react_native_1.Text style={[styles.dogfoodChoiceText, preferredModel === model.id && styles.dogfoodChoiceTextSelected]}>{model.name || model.id}</react_native_1.Text>
1127
+ </react_native_1.Pressable>))}
1128
+ </react_native_1.View>
1129
+ </>) : null}
1130
+ </react_native_1.View>) : null}
1131
+ {dogfoodExpandedStep === 'checkout' ? (<react_native_1.View style={styles.dogfoodExpandedPanel}>
1132
+ <react_native_1.Text style={styles.dogfoodStepLabel}>Git checkout on remote box</react_native_1.Text>
1133
+ {dogfoodProjects.map((project) => (<react_native_1.Pressable key={project.path} onPress={() => {
1134
+ setDogfoodProject(project);
1135
+ setDogfoodLane((0, DogfoodRuntime_1.defaultDogfoodLane)(project.framework || YaverFeedback_1.YaverFeedback.getDogfoodOnboarding()?.framework || 'expo'));
1136
+ setDogfoodRuntime(null);
1137
+ }} style={[styles.dogfoodProjectChoice, dogfoodProject?.path === project.path && styles.dogfoodChoiceSelected]}>
1138
+ <react_native_1.Text style={[styles.dogfoodChoiceText, dogfoodProject?.path === project.path && styles.dogfoodChoiceTextSelected]}>{project.name}</react_native_1.Text>
1139
+ <react_native_1.Text style={styles.dogfoodProjectDetail}>{dogfoodCheckoutDetail(project)}</react_native_1.Text>
1140
+ </react_native_1.Pressable>))}
1141
+ </react_native_1.View>) : null}
1142
+ <ActionRow label="Choose runtime" tint="#5645d8" onPress={() => {
1143
+ setDogfoodExpandedStep(null);
1144
+ setDogfoodSetupStage('lane');
1145
+ }} disabled={!dogfoodSetupReady}/>
1189
1146
  </>) : 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}/>
1147
+
1148
+ {dogfoodSetupStage === 'lane' ? (<>
1149
+ <react_native_1.View style={styles.dogfoodStageHeader}>
1150
+ <react_native_1.View style={styles.dogfoodStageCopy}>
1151
+ <react_native_1.Text style={styles.dogfoodStepLabel}>Runtime</react_native_1.Text>
1152
+ <react_native_1.Text style={styles.dogfoodStageTitle}>{dogfoodProject?.name} · {dogfoodFramework}</react_native_1.Text>
1153
+ </react_native_1.View>
1154
+ <react_native_1.Pressable onPress={() => setDogfoodSetupStage('setup')} style={styles.dogfoodSmallAction}>
1155
+ <react_native_1.Text style={styles.dogfoodSmallActionText}>Back</react_native_1.Text>
1156
+ </react_native_1.Pressable>
1157
+ </react_native_1.View>
1158
+ <DogfoodSessionUi_1.DogfoodLanePicker options={dogfoodLaneChoices} selected={dogfoodLane} fallbackLane={dogfoodLanePolicy.fallback} colors={FeedbackModalTheme_1.FEEDBACK_DOGFOOD_LIGHT_COLORS} onSelect={(lane) => {
1159
+ setDogfoodLane(lane);
1160
+ const appId = YaverFeedback_1.YaverFeedback.getDogfoodOnboarding()?.appId;
1161
+ if (appId)
1162
+ void (0, preferences_1.setPreferredDogfoodLane)(appId, lane);
1163
+ }}/>
1164
+ {dogfoodLane === 'webrtc' ? (<react_native_1.View style={styles.dogfoodExpandedPanel}>
1165
+ <react_native_1.Text style={styles.dogfoodStepLabel}>Simulator, emulator, or device</react_native_1.Text>
1166
+ {dogfoodNativeTargets.map((target) => (<react_native_1.Pressable key={target.id} disabled={!target.enabled} onPress={() => setDogfoodNativeTargetId(target.id)} style={[
1167
+ styles.dogfoodProjectChoice,
1168
+ dogfoodNativeTargetId === target.id && styles.dogfoodChoiceSelected,
1169
+ !target.enabled && styles.dogfoodChoiceDisabled,
1170
+ ]}>
1171
+ <react_native_1.Text style={[styles.dogfoodChoiceText, dogfoodNativeTargetId === target.id && styles.dogfoodChoiceTextSelected]}>{target.label}</react_native_1.Text>
1172
+ <react_native_1.Text style={styles.dogfoodProjectDetail}>{target.enabled
1173
+ ? [target.platform, target.displaySurface || target.surface, 'WebRTC'].filter(Boolean).join(' · ')
1174
+ : target.reason || 'Unavailable on this box'}</react_native_1.Text>
1175
+ </react_native_1.Pressable>))}
1176
+ </react_native_1.View>) : null}
1177
+ <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>
1178
+ <ActionRow label="Start Dogfood" tint="#5645d8" onPress={() => void startDogfoodRuntime()} disabled={dogfoodStartBlocked}/>
1179
+ </>) : null}
1180
+
1181
+ {dogfoodSetupStage === 'runtime' && dogfoodRuntime ? (<>
1182
+ <react_native_1.View style={styles.dogfoodStageHeader}>
1183
+ <react_native_1.View style={styles.dogfoodStageCopy}>
1184
+ <react_native_1.Text style={styles.dogfoodStepLabel}>Dogfooding</react_native_1.Text>
1185
+ <react_native_1.Text style={styles.dogfoodStageTitle}>{dogfoodSourceLabel}</react_native_1.Text>
1186
+ </react_native_1.View>
1187
+ <react_native_1.Pressable onPress={() => {
1188
+ void dogfoodControllerRef.current?.stop().catch(() => { });
1189
+ dogfoodControllerRef.current = null;
1190
+ setDogfoodRuntime(null);
1191
+ setDogfoodSetupStage('lane');
1192
+ }} style={styles.dogfoodSmallAction}>
1193
+ <react_native_1.Text style={styles.dogfoodSmallActionText}>Change</react_native_1.Text>
1194
+ </react_native_1.Pressable>
1195
+ </react_native_1.View>
1196
+ <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
1197
  {dogfoodRuntime.result?.url ? (<react_native_1.Pressable onPress={() => void react_native_1.Linking.openURL(dogfoodRuntime.result.url)} style={styles.dogfoodOpenPreview}>
1204
1198
  <react_native_1.Text style={styles.dogfoodOpenPreviewText}>Open dogfooded app</react_native_1.Text>
1205
1199
  </react_native_1.Pressable>) : null}
@@ -1392,7 +1386,7 @@ const FeedbackModal = () => {
1392
1386
  {availableReloadActions.map((reloadAction) => (<react_native_1.View key={reloadAction.id} style={styles.reloadRow}>
1393
1387
  <ActionRow label={reloadingId === reloadAction.id
1394
1388
  ? `${reloadAction.label}…`
1395
- : reloadAction.label} tint={reloadAction.id === 'rebuild' ? '#38bdf8' : '#fbbf24'} onPress={() => {
1389
+ : reloadAction.label} tint={reloadAction.id === 'rebuild' ? '#0369a1' : '#9a5700'} onPress={() => {
1396
1390
  void handleReloadAction(reloadAction);
1397
1391
  }}
1398
1392
  // Never `disabled` at the Pressable level for a blocked
@@ -1409,13 +1403,9 @@ const FeedbackModal = () => {
1409
1403
  </react_native_1.View>
1410
1404
 
1411
1405
  <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}>
1406
+ {/* Chat creates the first task, then VibeChatScreen owns the
1407
+ transcript and every MCP-backed follow-up. */}
1408
+ {!showVibeInput ? (<ActionRow label={action === 'vibing' ? 'Starting…' : 'Vibing'} tint="#5645d8" onPress={handleVibingButton} disabled={busy} busy={action === 'vibing'}/>) : (<react_native_1.View style={styles.vibeInputRow}>
1419
1409
  <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
1410
  <react_native_1.View style={styles.vibeInputButtons}>
1421
1411
  <react_native_1.Pressable onPress={() => { setShowVibeInput(false); setVibePrompt(''); }} style={({ pressed }) => [styles.vibeCancelBtn, pressed && styles.buttonPressed]} disabled={action === 'vibing'}>
@@ -1434,16 +1424,6 @@ const FeedbackModal = () => {
1434
1424
  Last vibing task: {lastVibeTaskId.slice(0, 12)}…
1435
1425
  </react_native_1.Text>)}
1436
1426
 
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
1427
  </react_native_1.View>
1448
1428
 
1449
1429
  {progress !== null && (<react_native_1.View style={styles.progressTrack}>
@@ -1462,9 +1442,8 @@ const FeedbackModal = () => {
1462
1442
  <react_native_1.Text style={styles.cancelBtnText}>Cancel</react_native_1.Text>
1463
1443
  </react_native_1.Pressable>
1464
1444
  </react_native_1.ScrollView>
1465
- </react_native_1.Pressable>
1466
- </react_native_1.KeyboardAvoidingView>
1467
- </react_native_1.Pressable>
1445
+ </react_native_1.View>
1446
+ </react_native_1.View>
1468
1447
  </react_native_1.Modal>)}
1469
1448
  {runnerAuthModal ? (<RunnerAuthNativeModal runner={runnerAuthModal} onClose={() => {
1470
1449
  setRunnerAuthModal(null);
@@ -1495,8 +1474,17 @@ const styles = react_native_1.StyleSheet.create({
1495
1474
  dogfoodChoiceRow: { flexDirection: 'row', gap: 7 },
1496
1475
  dogfoodChoice: { borderRadius: 9, borderWidth: 1, borderColor: '#d8d8e3', backgroundColor: '#fff', paddingHorizontal: 10, paddingVertical: 8 },
1497
1476
  dogfoodChoiceSelected: { borderColor: '#818cf8', backgroundColor: 'rgba(129,140,248,0.18)' },
1477
+ dogfoodChoiceDisabled: { opacity: 0.48 },
1498
1478
  dogfoodChoiceText: { color: '#666671', fontSize: 12, fontWeight: '700' },
1499
1479
  dogfoodChoiceTextSelected: { color: '#5645d8' },
1480
+ dogfoodExpandedPanel: { gap: 8, borderRadius: 11, borderWidth: 1, borderColor: '#d8d8e3', backgroundColor: 'rgba(255,255,255,0.72)', padding: 10 },
1481
+ dogfoodProjectChoice: { gap: 3, borderRadius: 9, borderWidth: 1, borderColor: '#d8d8e3', backgroundColor: '#fff', paddingHorizontal: 10, paddingVertical: 9 },
1482
+ dogfoodProjectDetail: { color: '#777783', fontSize: 10, lineHeight: 14 },
1483
+ dogfoodStageHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 10 },
1484
+ dogfoodStageCopy: { flex: 1, gap: 2 },
1485
+ dogfoodStageTitle: { color: '#222229', fontSize: 13, fontWeight: '700' },
1486
+ dogfoodSmallAction: { borderRadius: 8, borderWidth: 1, borderColor: '#d8d8e3', backgroundColor: '#fff', paddingHorizontal: 10, paddingVertical: 7 },
1487
+ dogfoodSmallActionText: { color: '#5645d8', fontSize: 11, fontWeight: '800' },
1500
1488
  dogfoodConsole: { maxHeight: 260, gap: 4, borderRadius: 11, padding: 10, backgroundColor: '#15151b' },
1501
1489
  dogfoodConsoleStatus: { color: '#a5b4fc', fontSize: 12, fontWeight: '800' },
1502
1490
  dogfoodConsoleLine: { color: '#d1d5db', fontSize: 10, lineHeight: 14, fontFamily: react_native_1.Platform.select({ ios: 'Menlo', android: 'monospace', default: 'monospace' }) },
@@ -1509,7 +1497,7 @@ const styles = react_native_1.StyleSheet.create({
1509
1497
  gap: 4,
1510
1498
  },
1511
1499
  reloadHint: {
1512
- color: '#8b8b93',
1500
+ color: '#666671',
1513
1501
  fontSize: 11,
1514
1502
  lineHeight: 15,
1515
1503
  paddingHorizontal: 4,
@@ -1541,7 +1529,7 @@ const styles = react_native_1.StyleSheet.create({
1541
1529
  backgroundColor: 'transparent',
1542
1530
  },
1543
1531
  vibeCancelBtnText: {
1544
- color: '#999',
1532
+ color: '#5f5f69',
1545
1533
  fontSize: 14,
1546
1534
  fontWeight: '600',
1547
1535
  },
@@ -1549,7 +1537,7 @@ const styles = react_native_1.StyleSheet.create({
1549
1537
  paddingHorizontal: 16,
1550
1538
  paddingVertical: 8,
1551
1539
  borderRadius: 8,
1552
- backgroundColor: '#818cf8',
1540
+ backgroundColor: '#5645d8',
1553
1541
  minWidth: 72,
1554
1542
  alignItems: 'center',
1555
1543
  },
@@ -1559,7 +1547,7 @@ const styles = react_native_1.StyleSheet.create({
1559
1547
  fontWeight: '700',
1560
1548
  },
1561
1549
  vibeTaskLine: {
1562
- color: '#818cf8',
1550
+ color: '#5645d8',
1563
1551
  fontSize: 12,
1564
1552
  marginTop: -4,
1565
1553
  fontFamily: react_native_1.Platform.select({ ios: 'Menlo', android: 'monospace', default: 'monospace' }),
@@ -1569,10 +1557,7 @@ const styles = react_native_1.StyleSheet.create({
1569
1557
  backgroundColor: 'rgba(0,0,0,0.55)',
1570
1558
  justifyContent: 'flex-end',
1571
1559
  },
1572
- kbAvoider: {
1573
- width: '100%',
1574
- justifyContent: 'flex-end',
1575
- },
1560
+ backdrop: react_native_1.StyleSheet.absoluteFillObject,
1576
1561
  modal: {
1577
1562
  backgroundColor: '#f8f8fb',
1578
1563
  borderTopLeftRadius: 22,
@@ -1631,7 +1616,7 @@ const styles = react_native_1.StyleSheet.create({
1631
1616
  tabs: { flexDirection: 'row', gap: 6, padding: 3, borderRadius: 12, backgroundColor: '#ededf3' },
1632
1617
  tab: { flex: 1, minHeight: 36, borderRadius: 9, alignItems: 'center', justifyContent: 'center' },
1633
1618
  tabSelected: { backgroundColor: '#fff' },
1634
- tabText: { color: '#858590', fontSize: 12, fontWeight: '700' },
1619
+ tabText: { color: '#666671', fontSize: 12, fontWeight: '700' },
1635
1620
  tabTextSelected: { color: '#6252e8' },
1636
1621
  tabContent: { gap: 12 },
1637
1622
  hidden: { display: 'none' },
@@ -1656,7 +1641,7 @@ const styles = react_native_1.StyleSheet.create({
1656
1641
  },
1657
1642
  machineRoutes: { flexDirection: 'row', gap: 8 },
1658
1643
  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' },
1644
+ machineRouteLabel: { color: '#666671', fontSize: 10, fontWeight: '700', textTransform: 'uppercase' },
1660
1645
  machineRouteValue: { color: '#222229', fontSize: 12, fontWeight: '700' },
1661
1646
  machineHeader: {
1662
1647
  flexDirection: 'row',
@@ -1692,7 +1677,7 @@ const styles = react_native_1.StyleSheet.create({
1692
1677
  letterSpacing: 0.8,
1693
1678
  },
1694
1679
  machineAction: {
1695
- color: '#a5b4fc',
1680
+ color: '#5645d8',
1696
1681
  fontSize: 12,
1697
1682
  fontWeight: '700',
1698
1683
  },
@@ -1723,7 +1708,7 @@ const styles = react_native_1.StyleSheet.create({
1723
1708
  },
1724
1709
  runnerSectionSubtitle: {
1725
1710
  marginTop: 2,
1726
- color: '#83838e',
1711
+ color: '#666671',
1727
1712
  fontSize: 12,
1728
1713
  },
1729
1714
  runnerRefreshBtn: {
@@ -1750,15 +1735,15 @@ const styles = react_native_1.StyleSheet.create({
1750
1735
  },
1751
1736
  runnerCardOk: {
1752
1737
  borderColor: 'rgba(34,197,94,0.28)',
1753
- backgroundColor: 'rgba(20,83,45,0.20)',
1738
+ backgroundColor: 'rgba(34,197,94,0.08)',
1754
1739
  },
1755
1740
  runnerCardWarning: {
1756
1741
  borderColor: 'rgba(251,191,36,0.28)',
1757
- backgroundColor: 'rgba(120,53,15,0.18)',
1742
+ backgroundColor: 'rgba(245,158,11,0.08)',
1758
1743
  },
1759
1744
  runnerCardError: {
1760
1745
  borderColor: 'rgba(248,113,113,0.28)',
1761
- backgroundColor: 'rgba(127,29,29,0.18)',
1746
+ backgroundColor: 'rgba(239,68,68,0.07)',
1762
1747
  },
1763
1748
  runnerCardTop: {
1764
1749
  flexDirection: 'row',
@@ -1777,13 +1762,13 @@ const styles = react_native_1.StyleSheet.create({
1777
1762
  color: '#666671',
1778
1763
  },
1779
1764
  runnerCardStatusOk: {
1780
- color: '#86efac',
1765
+ color: '#137a3f',
1781
1766
  },
1782
1767
  runnerCardStatusWarning: {
1783
- color: '#fcd34d',
1768
+ color: '#9a5700',
1784
1769
  },
1785
1770
  runnerCardStatusError: {
1786
- color: '#fca5a5',
1771
+ color: '#b42318',
1787
1772
  },
1788
1773
  runnerCardDetail: {
1789
1774
  color: '#858590',
@@ -1794,26 +1779,26 @@ const styles = react_native_1.StyleSheet.create({
1794
1779
  borderRadius: 10,
1795
1780
  borderWidth: 1,
1796
1781
  borderColor: 'rgba(129,140,248,0.35)',
1797
- backgroundColor: 'rgba(67,56,202,0.22)',
1782
+ backgroundColor: 'rgba(86,69,216,0.10)',
1798
1783
  paddingHorizontal: 12,
1799
1784
  paddingVertical: 8,
1800
1785
  },
1801
1786
  runnerActionBtnText: {
1802
- color: '#c7d2fe',
1787
+ color: '#5645d8',
1803
1788
  fontSize: 12,
1804
1789
  fontWeight: '700',
1805
1790
  },
1806
- runnerActionBtnSelected: { borderColor: '#818cf8', backgroundColor: 'rgba(79,70,229,0.44)' },
1791
+ runnerActionBtnSelected: { borderColor: '#6555df', backgroundColor: 'rgba(86,69,216,0.18)' },
1807
1792
  routingSummary: { flexDirection: 'row', alignItems: 'center', gap: 8, paddingHorizontal: 2 },
1808
1793
  routingSummaryLabel: { color: '#7c7c87', fontSize: 11, fontWeight: '600' },
1809
1794
  routingSummaryValue: { flex: 1, color: '#6555df', fontSize: 12, fontWeight: '700', textAlign: 'right' },
1810
1795
  modelChoiceRow: { gap: 6, paddingTop: 2 },
1811
1796
  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)' },
1797
+ modelChoiceSelected: { borderColor: '#6555df', backgroundColor: 'rgba(86,69,216,0.12)' },
1813
1798
  modelChoiceText: { color: '#73737e', fontSize: 11 },
1814
1799
  modelChoiceTextSelected: { color: '#5e4ce6', fontWeight: '700' },
1815
1800
  runnerSectionError: {
1816
- color: '#fca5a5',
1801
+ color: '#b42318',
1817
1802
  fontSize: 12,
1818
1803
  lineHeight: 18,
1819
1804
  },
@@ -1833,13 +1818,13 @@ const styles = react_native_1.StyleSheet.create({
1833
1818
  borderRadius: 3,
1834
1819
  },
1835
1820
  toast: {
1836
- color: '#22c55e',
1821
+ color: '#137a3f',
1837
1822
  fontSize: 13,
1838
1823
  textAlign: 'center',
1839
1824
  marginTop: 4,
1840
1825
  },
1841
1826
  error: {
1842
- color: '#ef4444',
1827
+ color: '#b42318',
1843
1828
  fontSize: 12,
1844
1829
  textAlign: 'center',
1845
1830
  marginTop: 4,