yaver-feedback-react-native 0.9.6 → 0.9.8

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.
@@ -0,0 +1,213 @@
1
+ import React from 'react';
2
+ import { Pressable, StyleSheet, Text, View } from 'react-native';
3
+ import type {
4
+ DogfoodFailure,
5
+ DogfoodLane,
6
+ DogfoodLaneOption,
7
+ DogfoodLogLine,
8
+ DogfoodPhase,
9
+ } from './DogfoodRuntime';
10
+
11
+ export type DogfoodStatusTone = 'ready' | 'attention' | 'blocked' | 'pending';
12
+
13
+ export interface DogfoodStatusStep {
14
+ key: string;
15
+ label: string;
16
+ detail: string;
17
+ tone: DogfoodStatusTone;
18
+ actionLabel?: string;
19
+ actionDisabled?: boolean;
20
+ expanded?: boolean;
21
+ onAction?: () => void;
22
+ }
23
+
24
+ export interface DogfoodUiColors {
25
+ background: string;
26
+ border: string;
27
+ text: string;
28
+ muted: string;
29
+ accent: string;
30
+ accentSoft: string;
31
+ ready: string;
32
+ attention: string;
33
+ blocked: string;
34
+ console: string;
35
+ }
36
+
37
+ const DEFAULT_COLORS: DogfoodUiColors = {
38
+ background: '#111827',
39
+ border: '#334155',
40
+ text: '#f8fafc',
41
+ muted: '#94a3b8',
42
+ accent: '#818cf8',
43
+ accentSoft: '#312e81',
44
+ ready: '#22c55e',
45
+ attention: '#f59e0b',
46
+ blocked: '#ef4444',
47
+ console: '#070b12',
48
+ };
49
+
50
+ function resolvedColors(colors?: Partial<DogfoodUiColors>): DogfoodUiColors {
51
+ return { ...DEFAULT_COLORS, ...colors };
52
+ }
53
+
54
+ function toneColor(tone: DogfoodStatusTone, colors: DogfoodUiColors): string {
55
+ if (tone === 'ready') return colors.ready;
56
+ if (tone === 'blocked') return colors.blocked;
57
+ if (tone === 'attention') return colors.attention;
58
+ return colors.muted;
59
+ }
60
+
61
+ /** Shared readiness rail used by Yaver itself and every embedded SDK host. */
62
+ export const DogfoodStatusRail: React.FC<{
63
+ steps: readonly DogfoodStatusStep[];
64
+ colors?: Partial<DogfoodUiColors>;
65
+ }> = ({ steps, colors: colorOverrides }) => {
66
+ const colors = resolvedColors(colorOverrides);
67
+ return (
68
+ <View style={styles.rail} accessibilityLabel="Dogfood session readiness">
69
+ {steps.map((step) => {
70
+ const tone = toneColor(step.tone, colors);
71
+ return (
72
+ <View key={step.key} style={styles.statusRow}>
73
+ <View style={[styles.statusDot, { backgroundColor: tone }]} />
74
+ <View style={styles.statusCopy}>
75
+ <Text style={[styles.statusLabel, { color: colors.text }]}>{step.label}</Text>
76
+ <Text style={[styles.statusDetail, { color: tone }]}>{step.detail}</Text>
77
+ </View>
78
+ {step.actionLabel && step.onAction ? (
79
+ <Pressable
80
+ accessibilityRole="button"
81
+ accessibilityLabel={`${step.actionLabel} ${step.label}`}
82
+ accessibilityState={{ expanded: step.expanded, disabled: step.actionDisabled }}
83
+ disabled={step.actionDisabled}
84
+ onPress={step.onAction}
85
+ style={({ pressed }) => [
86
+ styles.statusAction,
87
+ { borderColor: colors.border, opacity: step.actionDisabled ? 0.5 : pressed ? 0.7 : 1 },
88
+ ]}
89
+ >
90
+ <Text style={[styles.statusActionText, { color: colors.accent }]}>{step.actionLabel}</Text>
91
+ </Pressable>
92
+ ) : null}
93
+ </View>
94
+ );
95
+ })}
96
+ </View>
97
+ );
98
+ };
99
+
100
+ /** One lane selector and one default policy across Yaver, SFMG, and Talos. */
101
+ export const DogfoodLanePicker: React.FC<{
102
+ options: readonly DogfoodLaneOption[];
103
+ selected: DogfoodLane;
104
+ onSelect: (lane: DogfoodLane) => void;
105
+ colors?: Partial<DogfoodUiColors>;
106
+ showUnsupportedReasons?: boolean;
107
+ }> = ({ options, selected, onSelect, colors: colorOverrides, showUnsupportedReasons = true }) => {
108
+ const colors = resolvedColors(colorOverrides);
109
+ return (
110
+ <View accessibilityRole="radiogroup" accessibilityLabel="Dogfood runtime lane">
111
+ <View style={styles.choiceRow}>
112
+ {options.map((option) => {
113
+ const active = selected === option.lane;
114
+ return (
115
+ <Pressable
116
+ key={option.lane}
117
+ disabled={!option.supported}
118
+ onPress={() => onSelect(option.lane)}
119
+ accessibilityRole="radio"
120
+ accessibilityState={{ checked: active, disabled: !option.supported }}
121
+ style={({ pressed }) => [
122
+ styles.choice,
123
+ {
124
+ borderColor: active ? colors.accent : colors.border,
125
+ backgroundColor: active ? colors.accentSoft : colors.background,
126
+ opacity: !option.supported ? 0.45 : pressed ? 0.72 : 1,
127
+ },
128
+ ]}
129
+ >
130
+ <Text style={[styles.choiceText, { color: colors.text }, active && styles.choiceTextActive]}>
131
+ {option.label}{option.default ? ' · default' : ''}
132
+ </Text>
133
+ </Pressable>
134
+ );
135
+ })}
136
+ </View>
137
+ {showUnsupportedReasons ? options.filter((option) => !option.supported && option.reason).map((option) => (
138
+ <Text key={`${option.lane}-reason`} style={[styles.reason, { color: colors.muted }]}>
139
+ {option.label}: {option.reason}
140
+ </Text>
141
+ )) : null}
142
+ </View>
143
+ );
144
+ };
145
+
146
+ function runtimeTone(phase: DogfoodPhase, colors: DogfoodUiColors): string {
147
+ if (phase === 'ready') return colors.ready;
148
+ if (phase === 'failed') return colors.blocked;
149
+ if (phase === 'idle' || phase === 'stopped') return colors.muted;
150
+ return colors.attention;
151
+ }
152
+
153
+ /** Shared second-stage live console. Browser lane deliberately names Browser
154
+ * Logs; Hermes/WebRTC use the same lifecycle and failure/remedy treatment. */
155
+ export const DogfoodLiveConsole: React.FC<{
156
+ lane: DogfoodLane;
157
+ phase: DogfoodPhase;
158
+ message: string;
159
+ logs: readonly DogfoodLogLine[];
160
+ failure?: DogfoodFailure;
161
+ maxLines?: number;
162
+ colors?: Partial<DogfoodUiColors>;
163
+ renderText?: (text: string) => React.ReactNode;
164
+ }> = ({ lane, phase, message, logs, failure, maxLines = 80, colors: colorOverrides, renderText }) => {
165
+ const colors = resolvedColors(colorOverrides);
166
+ const text = logs.slice(-maxLines).map((line) => line.text).join('\n');
167
+ const title = lane === 'browser' ? 'Browser Logs' : lane === 'hermes' ? 'Hermes Logs' : 'WebRTC Logs';
168
+ return (
169
+ <View style={[styles.console, { backgroundColor: colors.console, borderColor: colors.border }]} accessibilityLabel={title}>
170
+ <View style={styles.consoleHeader}>
171
+ <View style={[styles.statusDot, { backgroundColor: runtimeTone(phase, colors) }]} />
172
+ <Text style={[styles.consoleTitle, { color: colors.text }]}>{title}</Text>
173
+ </View>
174
+ <Text style={[styles.consoleStatus, { color: colors.muted }]}>{message}</Text>
175
+ {text ? (
176
+ renderText ? renderText(text) : <Text selectable style={[styles.consoleText, { color: colors.text }]}>{text}</Text>
177
+ ) : (
178
+ <Text style={[styles.consoleEmpty, { color: colors.muted }]}>Waiting for the first line from the remote PC…</Text>
179
+ )}
180
+ {failure ? (
181
+ <View style={[styles.failure, { borderColor: colors.blocked }]}>
182
+ <Text style={[styles.failureText, { color: colors.text }]}>{failure.error}</Text>
183
+ <Text style={[styles.failureRemedy, { color: colors.muted }]}>{failure.remedy}</Text>
184
+ </View>
185
+ ) : null}
186
+ </View>
187
+ );
188
+ };
189
+
190
+ const styles = StyleSheet.create({
191
+ rail: { gap: 4 },
192
+ statusRow: { minHeight: 46, flexDirection: 'row', alignItems: 'center', gap: 9 },
193
+ statusDot: { width: 8, height: 8, borderRadius: 4 },
194
+ statusCopy: { flex: 1 },
195
+ statusLabel: { fontSize: 12, fontWeight: '700' },
196
+ statusDetail: { fontSize: 11, lineHeight: 16, marginTop: 1 },
197
+ statusAction: { borderWidth: 1, borderRadius: 8, paddingHorizontal: 10, paddingVertical: 7 },
198
+ statusActionText: { fontSize: 11, fontWeight: '700' },
199
+ choiceRow: { flexDirection: 'row', flexWrap: 'wrap', gap: 8 },
200
+ choice: { borderWidth: 1, borderRadius: 8, paddingHorizontal: 11, paddingVertical: 8 },
201
+ choiceText: { fontSize: 12, fontWeight: '500' },
202
+ choiceTextActive: { fontWeight: '700' },
203
+ reason: { fontSize: 10, lineHeight: 14, marginTop: 5 },
204
+ console: { width: '100%', maxHeight: 320, overflow: 'hidden', marginTop: 10, borderWidth: 1, borderRadius: 10, padding: 11, gap: 7 },
205
+ consoleHeader: { flexDirection: 'row', alignItems: 'center', gap: 7 },
206
+ consoleTitle: { fontSize: 12, fontWeight: '800' },
207
+ consoleStatus: { fontSize: 11, lineHeight: 16 },
208
+ consoleText: { fontFamily: 'monospace', fontSize: 10, lineHeight: 15 },
209
+ consoleEmpty: { fontSize: 10, fontStyle: 'italic' },
210
+ failure: { borderWidth: 1, borderRadius: 8, padding: 9, gap: 4 },
211
+ failureText: { fontSize: 11, fontWeight: '700' },
212
+ failureRemedy: { fontSize: 10, lineHeight: 15 },
213
+ });
@@ -38,6 +38,7 @@ import { AuthOverlay } from './AuthOverlay';
38
38
  import { QuickActionIcon } from './QuickActionIcon';
39
39
  import { YaverModeBadge } from './YaverModeBadge';
40
40
  import { VibeChatScreen } from './VibeChatScreen';
41
+ import { DogfoodQuickControls } from './DogfoodQuickControls';
41
42
  import { DeployPanel } from './DeployPanel';
42
43
  import { listReachableDevices, RemoteDevice } from './auth';
43
44
  import { reloadActions } from './reloadActions';
@@ -60,6 +61,7 @@ import {
60
61
  type DogfoodSnapshot,
61
62
  } from './DogfoodRuntime';
62
63
  import { createP2PDogfoodDriver } from './P2PDogfoodDriver';
64
+ import { DogfoodLanePicker, DogfoodLiveConsole, DogfoodStatusRail } from './DogfoodSessionUi';
63
65
 
64
66
  /**
65
67
  * Simplified feedback modal — launch scope is 3 actions:
@@ -1149,7 +1151,45 @@ export const FeedbackModal: React.FC = () => {
1149
1151
  && (selectedDogfoodRunner.ready || selectedDogfoodRunner.authConfigured);
1150
1152
  const dogfoodModelReady = !selectedDogfoodRunner?.models?.length
1151
1153
  || !!preferredModel && selectedDogfoodRunner.models.some((model) => model.id === preferredModel);
1152
- const dogfoodStartBlocked = !dogfoodProject || !dogfoodRunnerReady || !dogfoodModelReady;
1154
+ const dogfoodLaneChoices = dogfoodLaneOptions(
1155
+ dogfoodProject?.framework || YaverFeedback.getDogfoodOnboarding()?.framework || 'expo',
1156
+ { nativeRuntimeAvailable: dogfoodNativeAvailable },
1157
+ );
1158
+ const dogfoodLaneReady = dogfoodLaneChoices.some((option) => option.lane === dogfoodLane && option.supported);
1159
+ const dogfoodReadinessSteps = [
1160
+ {
1161
+ key: 'oauth', label: 'Yaver OAuth',
1162
+ detail: YaverFeedback.isAuthed() ? 'Signed in · session saved' : 'Sign in required',
1163
+ tone: YaverFeedback.isAuthed() ? 'ready' as const : 'attention' as const,
1164
+ },
1165
+ {
1166
+ key: 'machine', label: 'Remote PC',
1167
+ detail: machineCard.device ? machineCard.title : 'Choose a reachable development machine',
1168
+ tone: machineCard.device && machineCard.status === 'live' ? 'ready' as const : 'attention' as const,
1169
+ },
1170
+ {
1171
+ key: 'installation', label: 'This installation',
1172
+ detail: dogfoodEnrollment?.status === 'active' ? 'Device key approved' : dogfoodEnrollment?.status || 'Checking device key',
1173
+ tone: dogfoodEnrollment?.status === 'active' ? 'ready' as const
1174
+ : dogfoodEnrollment?.status === 'failed' ? 'blocked' as const : 'pending' as const,
1175
+ },
1176
+ {
1177
+ key: 'runner', label: 'Runner',
1178
+ detail: dogfoodRunnerReady ? selectedDogfoodRunner?.name || preferredRunner || 'Ready' : 'Choose or configure a coding runner',
1179
+ tone: dogfoodRunnerReady ? 'ready' as const : 'attention' as const,
1180
+ },
1181
+ {
1182
+ key: 'model', label: 'Model',
1183
+ detail: dogfoodModelReady ? preferredModel || 'Runner default' : 'Choose a model',
1184
+ tone: dogfoodModelReady ? 'ready' as const : 'attention' as const,
1185
+ },
1186
+ {
1187
+ key: 'lane', label: 'Runtime lane',
1188
+ detail: dogfoodLaneChoices.find((option) => option.lane === dogfoodLane)?.label || dogfoodLane,
1189
+ tone: dogfoodLaneReady ? 'ready' as const : 'blocked' as const,
1190
+ },
1191
+ ];
1192
+ const dogfoodStartBlocked = !dogfoodProject || !dogfoodRunnerReady || !dogfoodModelReady || !dogfoodLaneReady;
1153
1193
 
1154
1194
  // Once the user fires off a vibe task, swap the entire modal body
1155
1195
  // for the live chat screen. The chat manages its own SSE
@@ -1160,6 +1200,7 @@ export const FeedbackModal: React.FC = () => {
1160
1200
  return (
1161
1201
  <>
1162
1202
  <AuthOverlay />
1203
+ <DogfoodQuickControls />
1163
1204
  <QuickActionIcon />
1164
1205
  <YaverModeBadgeGate />
1165
1206
  <Modal
@@ -1201,6 +1242,7 @@ export const FeedbackModal: React.FC = () => {
1201
1242
  return (
1202
1243
  <>
1203
1244
  <AuthOverlay />
1245
+ <DogfoodQuickControls />
1204
1246
  <QuickActionIcon />
1205
1247
  <YaverModeBadgeGate />
1206
1248
  {visible && (
@@ -1289,6 +1331,7 @@ export const FeedbackModal: React.FC = () => {
1289
1331
  <Text style={styles.dogfoodWizardHint}>
1290
1332
  OAuth ✓ · machine {machineCard.device ? '✓' : 'required'} · installation {dogfoodEnrollment?.status || 'checking'}
1291
1333
  </Text>
1334
+ <DogfoodStatusRail steps={dogfoodReadinessSteps} />
1292
1335
  {dogfoodEnrollment?.installationId ? (
1293
1336
  <Text selectable style={styles.dogfoodInstallationId}>
1294
1337
  This device · {dogfoodEnrollment.installationId}
@@ -1372,30 +1415,15 @@ export const FeedbackModal: React.FC = () => {
1372
1415
  </>
1373
1416
  ) : null}
1374
1417
  <Text style={styles.dogfoodStepLabel}>Runtime lane</Text>
1375
- <View style={styles.dogfoodChoiceRow}>
1376
- {dogfoodLaneOptions(
1377
- dogfoodProject?.framework || YaverFeedback.getDogfoodOnboarding()?.framework || 'expo',
1378
- { nativeRuntimeAvailable: dogfoodNativeAvailable },
1379
- ).map((option) => (
1380
- <Pressable
1381
- key={option.lane}
1382
- onPress={() => {
1383
- if (!option.supported) return;
1384
- setDogfoodLane(option.lane);
1385
- const appId = YaverFeedback.getDogfoodOnboarding()?.appId;
1386
- if (appId) void setPreferredDogfoodLane(appId, option.lane);
1387
- }}
1388
- style={[
1389
- styles.dogfoodChoice,
1390
- dogfoodLane === option.lane && styles.dogfoodChoiceSelected,
1391
- !option.supported && styles.actionBtnDisabled,
1392
- ]}
1393
- accessibilityState={{ disabled: !option.supported, selected: dogfoodLane === option.lane }}
1394
- >
1395
- <Text style={[styles.dogfoodChoiceText, dogfoodLane === option.lane && styles.dogfoodChoiceTextSelected]}>{option.label}</Text>
1396
- </Pressable>
1397
- ))}
1398
- </View>
1418
+ <DogfoodLanePicker
1419
+ options={dogfoodLaneChoices}
1420
+ selected={dogfoodLane}
1421
+ onSelect={(lane) => {
1422
+ setDogfoodLane(lane);
1423
+ const appId = YaverFeedback.getDogfoodOnboarding()?.appId;
1424
+ if (appId) void setPreferredDogfoodLane(appId, lane);
1425
+ }}
1426
+ />
1399
1427
  <Text style={styles.dogfoodWizardHint}>
1400
1428
  {[preferredRunner || 'Choose a coding agent', preferredModel].filter(Boolean).join(' · ')}
1401
1429
  </Text>
@@ -1407,20 +1435,20 @@ export const FeedbackModal: React.FC = () => {
1407
1435
  busy={!!dogfoodRuntime && !['idle', 'ready', 'failed', 'stopped'].includes(dogfoodRuntime.phase)}
1408
1436
  />
1409
1437
  {dogfoodRuntime ? (
1410
- <View style={styles.dogfoodConsole}>
1411
- <Text style={styles.dogfoodConsoleStatus}>{dogfoodRuntime.message}</Text>
1412
- {dogfoodRuntime.logs.slice(-80).map((line, index) => (
1413
- <Text key={`${line.at}-${index}`} selectable style={styles.dogfoodConsoleLine}>{line.text}</Text>
1414
- ))}
1415
- {dogfoodRuntime.failure ? (
1416
- <Text style={styles.dogfoodConsoleError}>{dogfoodRuntime.failure.error}{'\n'}{dogfoodRuntime.failure.remedy}</Text>
1417
- ) : null}
1438
+ <>
1439
+ <DogfoodLiveConsole
1440
+ lane={dogfoodRuntime.project.lane}
1441
+ phase={dogfoodRuntime.phase}
1442
+ message={dogfoodRuntime.message}
1443
+ logs={dogfoodRuntime.logs}
1444
+ failure={dogfoodRuntime.failure}
1445
+ />
1418
1446
  {dogfoodRuntime.result?.url ? (
1419
1447
  <Pressable onPress={() => void Linking.openURL(dogfoodRuntime.result!.url!)} style={styles.dogfoodOpenPreview}>
1420
1448
  <Text style={styles.dogfoodOpenPreviewText}>Open dogfooded app</Text>
1421
1449
  </Pressable>
1422
1450
  ) : null}
1423
- </View>
1451
+ </>
1424
1452
  ) : null}
1425
1453
  </>
1426
1454
  )}