yaver-feedback-react-native 0.9.10 → 0.9.12

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.
@@ -316,12 +316,17 @@ const FeedbackModal = () => {
316
316
  return;
317
317
  await dogfoodControllerRef.current?.stop().catch(() => { });
318
318
  const framework = dogfoodProject.framework || onboarding.framework || 'expo';
319
+ const lanePlan = (0, DogfoodRuntime_1.dogfoodLanePlan)(framework, {
320
+ nativeRuntimeAvailable: dogfoodNativeAvailable,
321
+ browserRuntimeAvailable: dogfoodBrowserAvailable,
322
+ }, dogfoodLane);
319
323
  const controller = new DogfoodRuntime_1.DogfoodController({
320
324
  name: dogfoodProject.name,
321
325
  workDir: dogfoodProject.path,
322
326
  framework,
323
- lane: dogfoodLane,
324
- nativeTargetId: dogfoodLane === 'webrtc' ? dogfoodNativeTargetId : undefined,
327
+ lane: lanePlan.preferred,
328
+ fallbackLane: lanePlan.fallback,
329
+ nativeTargetId: lanePlan.preferred === 'webrtc' ? dogfoodNativeTargetId : undefined,
325
330
  }, (0, P2PDogfoodDriver_1.createP2PDogfoodDriver)(client), {
326
331
  onChange: (snapshot) => { if (mountedRef.current)
327
332
  setDogfoodRuntime(snapshot); },
@@ -415,10 +420,6 @@ const FeedbackModal = () => {
415
420
  status = 'attention';
416
421
  detail = 'Machine needs pairing again before feedback actions can run.';
417
422
  }
418
- else if (device.runnerDown) {
419
- status = 'attention';
420
- detail = 'Machine is online but the coding agent is down.';
421
- }
422
423
  else if (reachable === false) {
423
424
  status = 'offline';
424
425
  detail = 'Machine selected, but the agent is not responding.';
@@ -533,7 +534,11 @@ const FeedbackModal = () => {
533
534
  setShowVibeInput(authenticated || directDogfood);
534
535
  setVibePrompt('');
535
536
  if (onboarding) {
536
- setActiveTab(authenticated ? 'chat' : 'settings');
537
+ // A Dogfood shortcut is an explicit setup/runtime intent. Opening on
538
+ // Chat hid the machine/runner/checkout gate for signed-in SFMG users;
539
+ // keep the SDK-owned Dogfood surface visible, then show its live logs
540
+ // immediately when Start is tapped.
541
+ setActiveTab('settings');
537
542
  setDogfoodSetupStage('setup');
538
543
  setDogfoodExpandedStep(null);
539
544
  setDogfoodRuntime(null);
@@ -954,10 +959,15 @@ const FeedbackModal = () => {
954
959
  const dogfoodModelReady = !selectedDogfoodRunner?.models?.length
955
960
  || !!preferredModel && selectedDogfoodRunner.models.some((model) => model.id === preferredModel);
956
961
  const dogfoodFramework = dogfoodProject?.framework || YaverFeedback_1.YaverFeedback.getDogfoodOnboarding()?.framework || 'expo';
962
+ const dogfoodOnboarding = YaverFeedback_1.YaverFeedback.getDogfoodOnboarding();
957
963
  const dogfoodLaneChoices = (0, DogfoodRuntime_1.dogfoodLaneOptions)(dogfoodFramework, {
958
964
  nativeRuntimeAvailable: dogfoodNativeAvailable,
959
965
  browserRuntimeAvailable: dogfoodBrowserAvailable,
960
966
  });
967
+ const dogfoodLanePolicy = (0, DogfoodRuntime_1.dogfoodLanePlan)(dogfoodFramework, {
968
+ nativeRuntimeAvailable: dogfoodNativeAvailable,
969
+ browserRuntimeAvailable: dogfoodBrowserAvailable,
970
+ }, dogfoodLane);
961
971
  const selectedDogfoodNativeTarget = dogfoodNativeTargets.find((target) => target.id === dogfoodNativeTargetId) || null;
962
972
  const dogfoodLaneReady = dogfoodLaneChoices.some((option) => option.lane === dogfoodLane && option.supported)
963
973
  && (dogfoodLane !== 'webrtc' || !!selectedDogfoodNativeTarget?.enabled);
@@ -994,11 +1004,12 @@ const FeedbackModal = () => {
994
1004
  },
995
1005
  ];
996
1006
  const dogfoodStartBlocked = !dogfoodSetupReady || !dogfoodLaneReady;
997
- const dogfoodSourceLabel = dogfoodLane === 'webrtc'
1007
+ const activeDogfoodLane = dogfoodRuntime?.project.lane || dogfoodLane;
1008
+ const dogfoodSourceLabel = activeDogfoodLane === 'webrtc'
998
1009
  ? selectedDogfoodNativeTarget
999
1010
  ? [selectedDogfoodNativeTarget.label, selectedDogfoodNativeTarget.platform].filter(Boolean).join(' · ')
1000
1011
  : 'Native simulator, emulator, or device'
1001
- : dogfoodLane === 'hermes'
1012
+ : activeDogfoodLane === 'hermes'
1002
1013
  ? `Hermes build · ${machineCard.title}`
1003
1014
  : `${dogfoodFramework === 'flutter' ? 'Flutter web compiler' : 'Metro / browser build'} · ${machineCard.title}`;
1004
1015
  // Once the user fires off a vibe task, swap the entire modal body
@@ -1031,7 +1042,7 @@ const FeedbackModal = () => {
1031
1042
  <QuickActionIcon_1.QuickActionIcon />
1032
1043
  <YaverModeBadgeGate />
1033
1044
  {visible && (<react_native_1.Modal visible={visible} animationType="slide" transparent onRequestClose={handleClose}>
1034
- <react_native_1.View style={styles.overlay}>
1045
+ <react_native_1.KeyboardAvoidingView style={styles.overlay} behavior={react_native_1.Platform.OS === 'ios' ? 'padding' : 'height'}>
1035
1046
  <react_native_1.Pressable style={styles.backdrop} onPress={handleClose} accessibilityLabel="Close feedback"/>
1036
1047
  <react_native_1.View
1037
1048
  // Tablet: cap modal width and center as a card-style
@@ -1049,29 +1060,37 @@ const FeedbackModal = () => {
1049
1060
  }
1050
1061
  : null,
1051
1062
  ]}>
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'}>
1063
+ <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"
1064
+ // KeyboardAvoidingView is the single inset owner. Letting the
1065
+ // ScrollView add a second UIKit inset lifts the composer twice
1066
+ // on short phones and still leaves its action row clipped.
1067
+ automaticallyAdjustKeyboardInsets={false}>
1053
1068
  <react_native_1.View style={styles.header}>
1054
1069
  <react_native_1.Text style={styles.title}>
1055
- {dogfoodActive ? `${YaverFeedback_1.YaverFeedback.getDogfoodStatus().label || 'App'} Developer Mode` : 'Send Feedback'}
1070
+ {dogfoodOnboarding
1071
+ ? `Set up ${dogfoodOnboarding.projectName || dogfoodOnboarding.label || 'this app'} Dogfood`
1072
+ : dogfoodActive
1073
+ ? `${YaverFeedback_1.YaverFeedback.getDogfoodStatus().label || 'App'} Developer Mode`
1074
+ : 'Send Feedback'}
1056
1075
  </react_native_1.Text>
1057
1076
  <react_native_1.Pressable onPress={handleClose} hitSlop={12} style={styles.closeBtn} accessibilityRole="button" accessibilityLabel="Close">
1058
1077
  <react_native_1.Text style={styles.closeIcon}>×</react_native_1.Text>
1059
1078
  </react_native_1.Pressable>
1060
1079
  </react_native_1.View>
1061
1080
 
1062
- <react_native_1.View style={styles.tabs} accessibilityRole="tablist">
1081
+ {(!dogfoodOnboarding || dogfoodSetupStage === 'runtime') ? (<react_native_1.View style={styles.tabs} accessibilityRole="tablist">
1063
1082
  {['chat', 'settings'].map((tab) => (<react_native_1.Pressable key={tab} onPress={() => setActiveTab(tab)} style={[styles.tab, activeTab === tab && styles.tabSelected]} accessibilityRole="tab" accessibilityState={{ selected: activeTab === tab }}>
1064
1083
  <react_native_1.Text style={[styles.tabText, activeTab === tab && styles.tabTextSelected]}>{tab === 'chat' ? 'Chat' : 'Settings'}</react_native_1.Text>
1065
1084
  </react_native_1.Pressable>))}
1066
- </react_native_1.View>
1085
+ </react_native_1.View>) : null}
1067
1086
 
1068
1087
  <react_native_1.View style={[styles.tabContent, activeTab !== 'settings' && styles.hidden]}>
1069
1088
  <>
1070
- {YaverFeedback_1.YaverFeedback.getDogfoodOnboarding() ? (<react_native_1.View style={styles.dogfoodWizard}>
1071
- <react_native_1.Text style={styles.dogfoodWizardTitle}>Dogfood this app</react_native_1.Text>
1089
+ {dogfoodOnboarding ? (<react_native_1.View style={styles.dogfoodWizard}>
1090
+ <react_native_1.Text style={styles.dogfoodWizardTitle}>Connect the app to its checkout</react_native_1.Text>
1072
1091
  <react_native_1.Text style={styles.dogfoodWizardHint}>
1073
1092
  {dogfoodEnrollment?.status === 'active'
1074
- ? 'Signed in · this installation is approved'
1093
+ ? 'Choose one development machine, coding runner, and checkout. Then choose how to preview it.'
1075
1094
  : `Signed in · installation ${dogfoodEnrollment?.status || 'checking'}`}
1076
1095
  </react_native_1.Text>
1077
1096
  {dogfoodEnrollment?.status === 'active' && dogfoodSetupStage === 'setup' ? (<DogfoodSessionUi_1.DogfoodStatusRail steps={dogfoodSetupSteps} colors={FeedbackModalTheme_1.FEEDBACK_DOGFOOD_LIGHT_COLORS}/>) : null}
@@ -1125,7 +1144,7 @@ const FeedbackModal = () => {
1125
1144
  <react_native_1.Text style={styles.dogfoodProjectDetail}>{dogfoodCheckoutDetail(project)}</react_native_1.Text>
1126
1145
  </react_native_1.Pressable>))}
1127
1146
  </react_native_1.View>) : null}
1128
- <ActionRow label="Choose runtime" tint="#5645d8" onPress={() => {
1147
+ <ActionRow label={dogfoodSetupReady ? 'Continue to runtime' : 'Complete the choices above'} tint="#5645d8" onPress={() => {
1129
1148
  setDogfoodExpandedStep(null);
1130
1149
  setDogfoodSetupStage('lane');
1131
1150
  }} disabled={!dogfoodSetupReady}/>
@@ -1141,7 +1160,7 @@ const FeedbackModal = () => {
1141
1160
  <react_native_1.Text style={styles.dogfoodSmallActionText}>Back</react_native_1.Text>
1142
1161
  </react_native_1.Pressable>
1143
1162
  </react_native_1.View>
1144
- <DogfoodSessionUi_1.DogfoodLanePicker options={dogfoodLaneChoices} selected={dogfoodLane} colors={FeedbackModalTheme_1.FEEDBACK_DOGFOOD_LIGHT_COLORS} onSelect={(lane) => {
1163
+ <DogfoodSessionUi_1.DogfoodLanePicker options={dogfoodLaneChoices} selected={dogfoodLane} fallbackLane={dogfoodLanePolicy.fallback} colors={FeedbackModalTheme_1.FEEDBACK_DOGFOOD_LIGHT_COLORS} onSelect={(lane) => {
1145
1164
  setDogfoodLane(lane);
1146
1165
  const appId = YaverFeedback_1.YaverFeedback.getDogfoodOnboarding()?.appId;
1147
1166
  if (appId)
@@ -1186,26 +1205,27 @@ const FeedbackModal = () => {
1186
1205
  </>) : null}
1187
1206
  </>)}
1188
1207
  </react_native_1.View>) : null}
1208
+ {!dogfoodOnboarding ? <>
1189
1209
  <react_native_1.Pressable onPress={() => {
1190
- if (!YaverFeedback_1.YaverFeedback.isAuthed()) {
1191
- YaverFeedback_1.YaverFeedback.showLogin();
1192
- return;
1193
- }
1194
- YaverFeedback_1.YaverFeedback.showMachinePicker();
1195
- }} style={[
1196
- styles.machineCard,
1197
- machineCard.status === 'live' && styles.machineCardLive,
1198
- machineCard.status === 'attention' && styles.machineCardAttention,
1199
- machineCard.status === 'offline' && styles.machineCardOffline,
1200
- ]}>
1210
+ if (!YaverFeedback_1.YaverFeedback.isAuthed()) {
1211
+ YaverFeedback_1.YaverFeedback.showLogin();
1212
+ return;
1213
+ }
1214
+ YaverFeedback_1.YaverFeedback.showMachinePicker();
1215
+ }} style={[
1216
+ styles.machineCard,
1217
+ machineCard.status === 'live' && styles.machineCardLive,
1218
+ machineCard.status === 'attention' && styles.machineCardAttention,
1219
+ machineCard.status === 'offline' && styles.machineCardOffline,
1220
+ ]}>
1201
1221
  <react_native_1.View style={styles.machineHeader}>
1202
1222
  <react_native_1.View style={styles.machineTitleWrap}>
1203
1223
  <react_native_1.View style={[
1204
- styles.machineDot,
1205
- machineCard.status === 'live' && styles.machineDotLive,
1206
- machineCard.status === 'attention' && styles.machineDotAttention,
1207
- machineCard.status === 'offline' && styles.machineDotOffline,
1208
- ]}/>
1224
+ styles.machineDot,
1225
+ machineCard.status === 'live' && styles.machineDotLive,
1226
+ machineCard.status === 'attention' && styles.machineDotAttention,
1227
+ machineCard.status === 'offline' && styles.machineDotOffline,
1228
+ ]}/>
1209
1229
  <react_native_1.Text style={styles.machineLabel}>Coding Machine</react_native_1.Text>
1210
1230
  </react_native_1.View>
1211
1231
  <react_native_1.Text style={styles.machineAction}>
@@ -1235,14 +1255,14 @@ const FeedbackModal = () => {
1235
1255
  <react_native_1.Text style={styles.runnerSectionTitle}>Coding Agents</react_native_1.Text>
1236
1256
  <react_native_1.Text style={styles.runnerSectionSubtitle}>
1237
1257
  {runnerStatusLoading
1238
- ? 'Refreshing runner status on the selected machine…'
1239
- : `${readyRunnerCount} ready · ${needsAuthRunnerCount} need sign-in · ${missingRunnerCount} missing`}
1258
+ ? 'Refreshing runner status on the selected machine…'
1259
+ : `${readyRunnerCount} ready · ${needsAuthRunnerCount} need sign-in · ${missingRunnerCount} missing`}
1240
1260
  </react_native_1.Text>
1241
1261
  </react_native_1.View>
1242
1262
  <react_native_1.Pressable onPress={() => void loadRunnerStatuses()} style={({ pressed }) => [
1243
- styles.runnerRefreshBtn,
1244
- pressed && styles.buttonPressed,
1245
- ]} accessibilityRole="button" accessibilityLabel="Refresh coding-agent status">
1263
+ styles.runnerRefreshBtn,
1264
+ pressed && styles.buttonPressed,
1265
+ ]} accessibilityRole="button" accessibilityLabel="Refresh coding-agent status">
1246
1266
  <react_native_1.Text style={styles.runnerRefreshBtnText}>
1247
1267
  {runnerStatusLoading ? 'Refreshing…' : 'Refresh'}
1248
1268
  </react_native_1.Text>
@@ -1257,31 +1277,31 @@ const FeedbackModal = () => {
1257
1277
  </react_native_1.View>
1258
1278
 
1259
1279
  {runnerCards.map((row) => (<react_native_1.View key={row.id} style={[
1260
- styles.runnerCard,
1261
- row.tone === 'ok' && styles.runnerCardOk,
1262
- row.tone === 'warning' && styles.runnerCardWarning,
1263
- row.tone === 'error' && styles.runnerCardError,
1264
- ]}>
1280
+ styles.runnerCard,
1281
+ row.tone === 'ok' && styles.runnerCardOk,
1282
+ row.tone === 'warning' && styles.runnerCardWarning,
1283
+ row.tone === 'error' && styles.runnerCardError,
1284
+ ]}>
1265
1285
  <react_native_1.View style={styles.runnerCardTop}>
1266
1286
  <react_native_1.View style={{ flex: 1 }}>
1267
1287
  <react_native_1.Text style={styles.runnerCardTitle}>{row.name}</react_native_1.Text>
1268
1288
  <react_native_1.Text style={[
1269
- styles.runnerCardStatus,
1270
- row.tone === 'ok' && styles.runnerCardStatusOk,
1271
- row.tone === 'warning' && styles.runnerCardStatusWarning,
1272
- row.tone === 'error' && styles.runnerCardStatusError,
1273
- ]}>
1289
+ styles.runnerCardStatus,
1290
+ row.tone === 'ok' && styles.runnerCardStatusOk,
1291
+ row.tone === 'warning' && styles.runnerCardStatusWarning,
1292
+ row.tone === 'error' && styles.runnerCardStatusError,
1293
+ ]}>
1274
1294
  {row.statusLine}
1275
1295
  </react_native_1.Text>
1276
1296
  </react_native_1.View>
1277
1297
  <react_native_1.View style={styles.runnerCardActions}>
1278
1298
  {row.ready ? (<react_native_1.Pressable onPress={() => {
1279
- const nextModel = row.models?.find((model) => model.isDefault)?.id || row.models?.[0]?.id || '';
1280
- setPreferredRunnerState(row.id);
1281
- setPreferredModelState(nextModel);
1282
- void (0, preferences_1.setPreferredRunner)(row.id);
1283
- void (0, preferences_1.setPreferredModel)(nextModel || null);
1284
- }} style={({ pressed }) => [styles.runnerActionBtn, preferredRunner === row.id && styles.runnerActionBtnSelected, pressed && styles.buttonPressed]} accessibilityRole="button" accessibilityLabel={`Use ${row.name} for Vibing`}>
1299
+ const nextModel = row.models?.find((model) => model.isDefault)?.id || row.models?.[0]?.id || '';
1300
+ setPreferredRunnerState(row.id);
1301
+ setPreferredModelState(nextModel);
1302
+ void (0, preferences_1.setPreferredRunner)(row.id);
1303
+ void (0, preferences_1.setPreferredModel)(nextModel || null);
1304
+ }} style={({ pressed }) => [styles.runnerActionBtn, preferredRunner === row.id && styles.runnerActionBtnSelected, pressed && styles.buttonPressed]} accessibilityRole="button" accessibilityLabel={`Use ${row.name} for Vibing`}>
1285
1305
  <react_native_1.Text style={styles.runnerActionBtnText}>{preferredRunner === row.id ? 'Using' : 'Use'}</react_native_1.Text>
1286
1306
  </react_native_1.Pressable>) : null}
1287
1307
  {row.actionRunner ? (<react_native_1.Pressable onPress={() => setRunnerAuthModal(row.actionRunner ?? null)} style={({ pressed }) => [styles.runnerActionBtn, pressed && styles.buttonPressed]} accessibilityRole="button" accessibilityLabel={`${row.actionLabel} ${row.name}`}>
@@ -1291,9 +1311,9 @@ const FeedbackModal = () => {
1291
1311
  </react_native_1.View>
1292
1312
  {preferredRunner === row.id && (row.models?.length || 0) > 0 ? (<react_native_1.ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.modelChoiceRow}>
1293
1313
  {row.models.map((model) => (<react_native_1.Pressable key={model.id} onPress={() => {
1294
- setPreferredModelState(model.id);
1295
- void (0, preferences_1.setPreferredModel)(model.id);
1296
- }} style={[styles.modelChoice, preferredModel === model.id && styles.modelChoiceSelected]} accessibilityRole="button" accessibilityLabel={`Use ${model.name || model.id} model`}>
1314
+ setPreferredModelState(model.id);
1315
+ void (0, preferences_1.setPreferredModel)(model.id);
1316
+ }} style={[styles.modelChoice, preferredModel === model.id && styles.modelChoiceSelected]} accessibilityRole="button" accessibilityLabel={`Use ${model.name || model.id} model`}>
1297
1317
  <react_native_1.Text style={[styles.modelChoiceText, preferredModel === model.id && styles.modelChoiceTextSelected]}>{model.name || model.id}</react_native_1.Text>
1298
1318
  </react_native_1.Pressable>))}
1299
1319
  </react_native_1.ScrollView>) : null}
@@ -1308,22 +1328,22 @@ const FeedbackModal = () => {
1308
1328
  Quick access icon is hidden. Shake the phone if you want feedback back fast.
1309
1329
  </react_native_1.Text>
1310
1330
  <react_native_1.Pressable onPress={() => {
1311
- void YaverFeedback_1.YaverFeedback.setQuickIconVisible(true);
1312
- setQuickIconHidden(false);
1313
- }} style={({ pressed }) => [
1314
- styles.quickIconToggle,
1315
- pressed && styles.buttonPressed,
1316
- ]}>
1331
+ void YaverFeedback_1.YaverFeedback.setQuickIconVisible(true);
1332
+ setQuickIconHidden(false);
1333
+ }} style={({ pressed }) => [
1334
+ styles.quickIconToggle,
1335
+ pressed && styles.buttonPressed,
1336
+ ]}>
1317
1337
  <react_native_1.Text style={styles.quickIconToggleText}>Show quick icon again</react_native_1.Text>
1318
1338
  </react_native_1.Pressable>
1319
1339
  </react_native_1.View>)}
1320
1340
 
1321
1341
  {YaverFeedback_1.YaverFeedback.isAuthed() ? (<react_native_1.Pressable onPress={() => {
1322
- void YaverFeedback_1.YaverFeedback.signOut().then(() => {
1323
- handleClose();
1324
- YaverFeedback_1.YaverFeedback.showLogin();
1325
- });
1326
- }} style={({ pressed }) => [styles.yaverSignOutBtn, pressed && styles.buttonPressed]} accessibilityRole="button" accessibilityLabel="Sign out of Yaver">
1342
+ void YaverFeedback_1.YaverFeedback.signOut().then(() => {
1343
+ handleClose();
1344
+ YaverFeedback_1.YaverFeedback.showLogin();
1345
+ });
1346
+ }} style={({ pressed }) => [styles.yaverSignOutBtn, pressed && styles.buttonPressed]} accessibilityRole="button" accessibilityLabel="Sign out of Yaver">
1327
1347
  <react_native_1.Text style={styles.yaverSignOutText}>Sign out of Yaver</react_native_1.Text>
1328
1348
  </react_native_1.Pressable>) : null}
1329
1349
 
@@ -1334,57 +1354,58 @@ const FeedbackModal = () => {
1334
1354
  </react_native_1.Text>
1335
1355
  <react_native_1.View style={styles.iconSelectorGrid}>
1336
1356
  {Object.entries(preferences_1.QUICK_ICON_COLOR_PRESETS).map(([preset, colors]) => {
1337
- const selected = quickIconColorPreset === preset;
1338
- return (<react_native_1.Pressable key={preset} onPress={() => {
1339
- setQuickIconColorPreset(preset);
1340
- void YaverFeedback_1.YaverFeedback.setQuickIconColorPreset(preset);
1341
- }} style={[
1342
- styles.iconOption,
1343
- iconOptionWidthOverride ? { width: iconOptionWidthOverride } : null,
1344
- selected && styles.iconOptionSelected,
1345
- ]}>
1357
+ const selected = quickIconColorPreset === preset;
1358
+ return (<react_native_1.Pressable key={preset} onPress={() => {
1359
+ setQuickIconColorPreset(preset);
1360
+ void YaverFeedback_1.YaverFeedback.setQuickIconColorPreset(preset);
1361
+ }} style={[
1362
+ styles.iconOption,
1363
+ iconOptionWidthOverride ? { width: iconOptionWidthOverride } : null,
1364
+ selected && styles.iconOptionSelected,
1365
+ ]}>
1346
1366
  <react_native_1.View style={[
1347
- styles.iconOptionCircle,
1348
- {
1349
- backgroundColor: colors.backgroundColor,
1350
- borderColor: colors.borderColor,
1351
- shadowColor: colors.shadowColor,
1352
- },
1353
- ]}>
1367
+ styles.iconOptionCircle,
1368
+ {
1369
+ backgroundColor: colors.backgroundColor,
1370
+ borderColor: colors.borderColor,
1371
+ shadowColor: colors.shadowColor,
1372
+ },
1373
+ ]}>
1354
1374
  <react_native_1.Text style={[
1355
- styles.iconOptionLabel,
1356
- { color: colors.foregroundColor },
1357
- ]}>
1375
+ styles.iconOptionLabel,
1376
+ { color: colors.foregroundColor },
1377
+ ]}>
1358
1378
  y
1359
1379
  </react_native_1.Text>
1360
1380
  </react_native_1.View>
1361
1381
  <react_native_1.Text style={styles.iconOptionText}>{colors.label}</react_native_1.Text>
1362
1382
  </react_native_1.Pressable>);
1363
- })}
1383
+ })}
1364
1384
  </react_native_1.View>
1365
1385
  </react_native_1.View>
1366
1386
 
1367
1387
  {/* 1. Reload — Hot / Full / Rebuild Bundle.
1368
- Rendered from the shared decision seam, so a production
1369
- build (__DEV__ false) renders nothing here at all, and a
1370
- blocked action shows greyed WITH its reason underneath
1371
- rather than vanishing. */}
1388
+ Rendered from the shared decision seam, so a production
1389
+ build (__DEV__ false) renders nothing here at all, and a
1390
+ blocked action shows greyed WITH its reason underneath
1391
+ rather than vanishing. */}
1372
1392
  {availableReloadActions.map((reloadAction) => (<react_native_1.View key={reloadAction.id} style={styles.reloadRow}>
1373
1393
  <ActionRow label={reloadingId === reloadAction.id
1374
- ? `${reloadAction.label}…`
1375
- : reloadAction.label} tint={reloadAction.id === 'rebuild' ? '#0369a1' : '#9a5700'} onPress={() => {
1376
- void handleReloadAction(reloadAction);
1377
- }}
1378
- // Never `disabled` at the Pressable level for a blocked
1379
- // action: we WANT the tap so we can say why. Only a
1380
- // genuinely busy modal blocks the press.
1381
- disabled={busy && reloadingId !== reloadAction.id} busy={reloadingId === reloadAction.id}/>
1394
+ ? `${reloadAction.label}…`
1395
+ : reloadAction.label} tint={reloadAction.id === 'rebuild' ? '#0369a1' : '#9a5700'} onPress={() => {
1396
+ void handleReloadAction(reloadAction);
1397
+ }}
1398
+ // Never `disabled` at the Pressable level for a blocked
1399
+ // action: we WANT the tap so we can say why. Only a
1400
+ // genuinely busy modal blocks the press.
1401
+ disabled={busy && reloadingId !== reloadAction.id} busy={reloadingId === reloadAction.id}/>
1382
1402
  <react_native_1.Text style={styles.reloadHint}>
1383
1403
  {reloadAction.enabled
1384
- ? reloadAction.hint
1385
- : reloadAction.disabledReason}
1404
+ ? reloadAction.hint
1405
+ : reloadAction.disabledReason}
1386
1406
  </react_native_1.Text>
1387
1407
  </react_native_1.View>))}
1408
+ </> : null}
1388
1409
  </>
1389
1410
  </react_native_1.View>
1390
1411
 
@@ -1429,7 +1450,7 @@ const FeedbackModal = () => {
1429
1450
  </react_native_1.Pressable>
1430
1451
  </react_native_1.ScrollView>
1431
1452
  </react_native_1.View>
1432
- </react_native_1.View>
1453
+ </react_native_1.KeyboardAvoidingView>
1433
1454
  </react_native_1.Modal>)}
1434
1455
  {runnerAuthModal ? (<RunnerAuthNativeModal runner={runnerAuthModal} onClose={() => {
1435
1456
  setRunnerAuthModal(null);
@@ -4,6 +4,8 @@ export interface YaverLoginScreenProps {
4
4
  onLoggedIn: (token: string) => void;
5
5
  /** Optional cancel button shown in header. */
6
6
  onCancel?: () => void;
7
+ /** Intent shown under the Yaver mark. Defaults to ordinary feedback. */
8
+ subtitle?: string;
7
9
  }
8
10
  /**
9
11
  * Full-screen in-SDK login. Mirrors the Yaver mobile app login UX: native
@@ -91,7 +91,7 @@ const iconStyles = react_native_1.StyleSheet.create({
91
91
  * Apple Sign-In on iOS, in-app browser OAuth for Google/GitHub/GitLab/
92
92
  * Microsoft (no codes, no leaving the app), and inline email/password.
93
93
  */
94
- const YaverLoginScreen = ({ onLoggedIn, onCancel, }) => {
94
+ const YaverLoginScreen = ({ onLoggedIn, onCancel, subtitle = 'Sign in to send feedback', }) => {
95
95
  const [busyProvider, setBusyProvider] = (0, react_1.useState)(null);
96
96
  const [showEmailForm, setShowEmailForm] = (0, react_1.useState)(false);
97
97
  const [isSignUp, setIsSignUp] = (0, react_1.useState)(false);
@@ -186,7 +186,7 @@ const YaverLoginScreen = ({ onLoggedIn, onCancel, }) => {
186
186
 
187
187
  <react_native_1.View style={styles.header}>
188
188
  <react_native_1.Text style={styles.logo}>Yaver</react_native_1.Text>
189
- <react_native_1.Text style={styles.subtitle}>Sign in to send feedback</react_native_1.Text>
189
+ <react_native_1.Text style={styles.subtitle}>{subtitle}</react_native_1.Text>
190
190
  </react_native_1.View>
191
191
 
192
192
  <react_native_1.View style={styles.buttons}>
@@ -4,8 +4,10 @@ export interface YaverMachinePickerProps {
4
4
  token: string;
5
5
  /** Currently-selected deviceId (from config / cache) — highlighted. */
6
6
  currentDeviceId?: string;
7
- onPick: (device: RemoteDevice) => void;
7
+ onPick: (device: RemoteDevice) => void | Promise<void>;
8
8
  onCancel?: () => void;
9
+ /** Optional flow-specific title, e.g. "Choose a machine for SFMG". */
10
+ title?: string;
9
11
  }
10
12
  /**
11
13
  * List of remote dev machines owned by the signed-in user.
@@ -45,14 +45,18 @@ const PairDeviceModal_1 = require("./PairDeviceModal");
45
45
  * SDK then uses that device's deviceId for agent discovery (LAN probe +
46
46
  * relay fallback through Convex).
47
47
  */
48
- const YaverMachinePickerScreen = ({ token, currentDeviceId, onPick, onCancel, }) => {
48
+ const YaverMachinePickerScreen = ({ token, currentDeviceId, onPick, onCancel, title = 'Choose a machine', }) => {
49
49
  const [loading, setLoading] = (0, react_1.useState)(true);
50
50
  const [refreshing, setRefreshing] = (0, react_1.useState)(false);
51
51
  const [error, setError] = (0, react_1.useState)(null);
52
52
  const [list, setList] = (0, react_1.useState)({ owned: [] });
53
53
  const [pairingDevice, setPairingDevice] = (0, react_1.useState)(null);
54
54
  const [reachability, setReachability] = (0, react_1.useState)({});
55
+ const [selectingDeviceId, setSelectingDeviceId] = (0, react_1.useState)(null);
56
+ const mountedRef = (0, react_1.useRef)(true);
57
+ const loadGenerationRef = (0, react_1.useRef)(0);
55
58
  const load = (0, react_1.useCallback)(async (silent = false) => {
59
+ const generation = ++loadGenerationRef.current;
56
60
  if (!silent)
57
61
  setLoading(true);
58
62
  setError(null);
@@ -60,22 +64,17 @@ const YaverMachinePickerScreen = ({ token, currentDeviceId, onPick, onCancel, })
60
64
  const result = await (0, auth_1.listReachableDevices)(token);
61
65
  setList(result);
62
66
  setReachability({});
63
- void (async () => {
64
- const devices = result.owned;
65
- const settled = await Promise.allSettled(devices.map(async (device) => ({
66
- deviceId: device.deviceId,
67
- result: await (0, auth_1.probeDeviceReachability)(device),
68
- })));
69
- setReachability((prev) => {
70
- const next = { ...prev };
71
- for (const entry of settled) {
72
- if (entry.status === 'fulfilled') {
73
- next[entry.value.deviceId] = entry.value.result;
74
- }
75
- }
76
- return next;
77
- });
78
- })();
67
+ // Convex already gives us a fresh, heartbeat-gated online answer. Render
68
+ // that immediately. Only probe cloud-offline rows to detect a phone-LAN
69
+ // route that came back before the next heartbeat, and publish each result
70
+ // as it arrives instead of holding the whole list behind the slowest box.
71
+ for (const device of result.owned.filter((candidate) => !candidate.isOnline)) {
72
+ void (0, auth_1.probeDeviceReachability)(device).then((probe) => {
73
+ if (!mountedRef.current || loadGenerationRef.current !== generation)
74
+ return;
75
+ setReachability((prev) => ({ ...prev, [device.deviceId]: probe }));
76
+ }).catch(() => { });
77
+ }
79
78
  if (result.owned.length === 0) {
80
79
  setError('No machines found yet. Run `yaver auth` + `yaver serve` on your machine.');
81
80
  }
@@ -89,7 +88,9 @@ const YaverMachinePickerScreen = ({ token, currentDeviceId, onPick, onCancel, })
89
88
  }
90
89
  }, [token]);
91
90
  (0, react_1.useEffect)(() => {
91
+ mountedRef.current = true;
92
92
  void load();
93
+ return () => { mountedRef.current = false; };
93
94
  }, [load]);
94
95
  const handlePick = async (device) => {
95
96
  // Needs-auth device — show the in-SDK pair modal instead of
@@ -101,7 +102,9 @@ const YaverMachinePickerScreen = ({ token, currentDeviceId, onPick, onCancel, })
101
102
  setPairingDevice(device);
102
103
  return;
103
104
  }
104
- const direct = await (0, auth_1.probeDeviceReachability)(device);
105
+ const direct = device.isOnline
106
+ ? { reachable: true }
107
+ : await (0, auth_1.probeDeviceReachability)(device);
105
108
  // Do not hard-block selection just because the LAN /health probe
106
109
  // failed. The standalone SDK can still reach a healthy machine via
107
110
  // the normal selected-device discovery path (including relay), and
@@ -114,11 +117,19 @@ const YaverMachinePickerScreen = ({ token, currentDeviceId, onPick, onCancel, })
114
117
  setReachability((prev) => ({ ...prev, [device.deviceId]: direct }));
115
118
  return;
116
119
  }
117
- await (0, auth_1.saveSelectedDeviceId)(device.deviceId);
118
- onPick(device);
120
+ setSelectingDeviceId(device.deviceId);
121
+ try {
122
+ await (0, auth_1.saveSelectedDeviceId)(device.deviceId);
123
+ await onPick(device);
124
+ }
125
+ finally {
126
+ if (mountedRef.current)
127
+ setSelectingDeviceId(null);
128
+ }
119
129
  };
120
130
  const renderDevice = (device) => {
121
131
  const selected = device.deviceId === currentDeviceId;
132
+ const selecting = device.deviceId === selectingDeviceId;
122
133
  const probe = reachability[device.deviceId];
123
134
  // Trust Convex's `isOnline` — the backend already gates it on a
124
135
  // fresh 90 s heartbeat (see backend/convex/devices.ts
@@ -137,14 +148,17 @@ const YaverMachinePickerScreen = ({ token, currentDeviceId, onPick, onCancel, })
137
148
  : effectivelyReachable
138
149
  ? '#22c55e'
139
150
  : device.isOnline
140
- ? '#f59e0b'
151
+ ? '#22c55e'
141
152
  : explicitlyOffline || !device.isOnline
142
153
  ? '#ef4444'
143
154
  : '#22c55e';
144
155
  // Derive a single short status phrase the user can act on.
145
156
  let statusLine = device.platform;
146
- if (probe === undefined) {
147
- statusLine = 'Checking connection…';
157
+ if (selecting) {
158
+ statusLine = 'Connecting…';
159
+ }
160
+ else if (probe === undefined && device.isOnline) {
161
+ statusLine = device.platform || 'Online';
148
162
  }
149
163
  else if (!device.isOnline && effectivelyReachable) {
150
164
  statusLine = 'Reachable now — waiting for cloud status to refresh';
@@ -166,20 +180,21 @@ const YaverMachinePickerScreen = ({ token, currentDeviceId, onPick, onCancel, })
166
180
  // Happy-path subtitle.
167
181
  statusLine = device.platform;
168
182
  }
169
- return (<react_native_1.TouchableOpacity key={device.deviceId} style={[styles.deviceRow, selected && styles.deviceSelected]} onPress={() => handlePick(device)}>
183
+ return (<react_native_1.TouchableOpacity key={device.deviceId} style={[styles.deviceRow, selected && styles.deviceSelected]} onPress={() => handlePick(device)} disabled={selectingDeviceId !== null}>
170
184
  <react_native_1.View style={[styles.health, { backgroundColor: healthColor }]}/>
171
185
  <react_native_1.View style={{ flex: 1 }}>
172
186
  <react_native_1.Text style={styles.deviceName}>{device.name || device.deviceId}</react_native_1.Text>
173
187
  <react_native_1.Text style={styles.deviceMeta}>{statusLine}</react_native_1.Text>
174
188
  </react_native_1.View>
175
- {selected && <react_native_1.Text style={styles.selectedBadge}>seçili</react_native_1.Text>}
189
+ {selecting ? <react_native_1.ActivityIndicator color="#a5b4fc" size="small"/> : null}
190
+ {selected && !selecting && <react_native_1.Text style={styles.selectedBadge}>selected</react_native_1.Text>}
176
191
  </react_native_1.TouchableOpacity>);
177
192
  };
178
193
  return (<react_native_1.SafeAreaView style={styles.container}>
179
194
  <react_native_1.View style={styles.header}>
180
- <react_native_1.Text style={styles.title}>Makine Seç</react_native_1.Text>
195
+ <react_native_1.Text style={styles.title}>{title}</react_native_1.Text>
181
196
  {onCancel && (<react_native_1.TouchableOpacity onPress={onCancel} style={styles.cancel}>
182
- <react_native_1.Text style={styles.cancelText}>Kapat</react_native_1.Text>
197
+ <react_native_1.Text style={styles.cancelText}>Close</react_native_1.Text>
183
198
  </react_native_1.TouchableOpacity>)}
184
199
  </react_native_1.View>
185
200
 
@@ -189,7 +204,7 @@ const YaverMachinePickerScreen = ({ token, currentDeviceId, onPick, onCancel, })
189
204
  }} tintColor="#6366f1"/>}>
190
205
  {loading ? (<react_native_1.ActivityIndicator color="#6366f1" style={{ marginTop: 60 }}/>) : (<>
191
206
  {list.owned.length > 0 && (<react_native_1.View style={styles.section}>
192
- <react_native_1.Text style={styles.sectionTitle}>Kendi makinelerim</react_native_1.Text>
207
+ <react_native_1.Text style={styles.sectionTitle}>Your machines</react_native_1.Text>
193
208
  {list.owned.map(renderDevice)}
194
209
  </react_native_1.View>)}
195
210
  {error && <react_native_1.Text style={styles.error}>{error}</react_native_1.Text>}