yaver-feedback-react-native 0.9.10 → 0.9.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DogfoodRuntime.d.ts +26 -0
- package/dist/DogfoodRuntime.js +85 -11
- package/dist/DogfoodSessionUi.d.ts +1 -0
- package/dist/DogfoodSessionUi.js +3 -2
- package/dist/FeedbackModal.js +20 -6
- package/dist/__tests__/DogfoodRuntime.test.js +45 -0
- package/dist/__tests__/FeedbackModalContract.test.js +9 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -2
- package/package.json +1 -1
- package/src/DogfoodRuntime.ts +108 -11
- package/src/DogfoodSessionUi.tsx +4 -2
- package/src/FeedbackModal.tsx +21 -5
- package/src/__tests__/DogfoodRuntime.test.ts +51 -0
- package/src/__tests__/FeedbackModalContract.test.ts +10 -2
- package/src/index.ts +2 -0
package/dist/DogfoodRuntime.d.ts
CHANGED
|
@@ -17,6 +17,13 @@ export interface DogfoodProject {
|
|
|
17
17
|
workDir: string;
|
|
18
18
|
framework: string;
|
|
19
19
|
lane: DogfoodLane;
|
|
20
|
+
/**
|
|
21
|
+
* Optional automatic recovery lane. The shared onboarding flow uses the
|
|
22
|
+
* browser lane here when a user prefers Hermes or WebRTC for a project that
|
|
23
|
+
* can also render in a browser. The failed preferred attempt remains in the
|
|
24
|
+
* live console; the fallback is never silent.
|
|
25
|
+
*/
|
|
26
|
+
fallbackLane?: DogfoodLane;
|
|
20
27
|
/** Optional source URL for drivers that can clone missing source. */
|
|
21
28
|
repositoryUrl?: string;
|
|
22
29
|
/** Optional native target from /remote-runtime/capabilities for WebRTC. */
|
|
@@ -29,6 +36,12 @@ export interface DogfoodLaneOption {
|
|
|
29
36
|
default: boolean;
|
|
30
37
|
reason?: string;
|
|
31
38
|
}
|
|
39
|
+
export interface DogfoodLanePlan {
|
|
40
|
+
preferred: DogfoodLane;
|
|
41
|
+
/** Browser is the cheap, portable recovery lane for RN/Expo/Flutter/web. */
|
|
42
|
+
fallback?: DogfoodLane;
|
|
43
|
+
options: DogfoodLaneOption[];
|
|
44
|
+
}
|
|
32
45
|
/** One framework-to-lane matrix for Yaver and third-party consumers. */
|
|
33
46
|
export declare function dogfoodLaneOptions(framework: string, capabilities?: {
|
|
34
47
|
nativeRuntimeAvailable?: boolean;
|
|
@@ -40,6 +53,19 @@ export declare function defaultDogfoodLane(framework: string, capabilities?: {
|
|
|
40
53
|
browserRuntimeAvailable?: boolean;
|
|
41
54
|
selfDevelopment?: boolean;
|
|
42
55
|
}): DogfoodLane;
|
|
56
|
+
/**
|
|
57
|
+
* Resolve one ordered lane policy for Yaver and embedded SDK hosts.
|
|
58
|
+
*
|
|
59
|
+
* Browser is the onboarding default for browser-capable React Native, Expo,
|
|
60
|
+
* Flutter, and web projects. When the user explicitly prefers Hermes or
|
|
61
|
+
* WebRTC, browser becomes the automatic second attempt. Native-only projects
|
|
62
|
+
* remain WebRTC-only and never advertise a browser recovery they cannot run.
|
|
63
|
+
*/
|
|
64
|
+
export declare function dogfoodLanePlan(framework: string, capabilities?: {
|
|
65
|
+
nativeRuntimeAvailable?: boolean;
|
|
66
|
+
browserRuntimeAvailable?: boolean;
|
|
67
|
+
selfDevelopment?: boolean;
|
|
68
|
+
}, preferred?: DogfoodLane | null): DogfoodLanePlan;
|
|
43
69
|
export interface DogfoodLogLine {
|
|
44
70
|
text: string;
|
|
45
71
|
at: number;
|
package/dist/DogfoodRuntime.js
CHANGED
|
@@ -15,6 +15,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
exports.DogfoodController = exports.dogfoodLogLinesFromDevEvent = exports.DogfoodRuntimeError = void 0;
|
|
16
16
|
exports.dogfoodLaneOptions = dogfoodLaneOptions;
|
|
17
17
|
exports.defaultDogfoodLane = defaultDogfoodLane;
|
|
18
|
+
exports.dogfoodLanePlan = dogfoodLanePlan;
|
|
18
19
|
exports.validateDogfoodProject = validateDogfoodProject;
|
|
19
20
|
exports.runtimeLogLinesFromDevEvent = runtimeLogLinesFromDevEvent;
|
|
20
21
|
/** One framework-to-lane matrix for Yaver and third-party consumers. */
|
|
@@ -49,6 +50,26 @@ function defaultDogfoodLane(framework, capabilities = {}) {
|
|
|
49
50
|
|| options.find((option) => option.supported)?.lane
|
|
50
51
|
|| 'browser';
|
|
51
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Resolve one ordered lane policy for Yaver and embedded SDK hosts.
|
|
55
|
+
*
|
|
56
|
+
* Browser is the onboarding default for browser-capable React Native, Expo,
|
|
57
|
+
* Flutter, and web projects. When the user explicitly prefers Hermes or
|
|
58
|
+
* WebRTC, browser becomes the automatic second attempt. Native-only projects
|
|
59
|
+
* remain WebRTC-only and never advertise a browser recovery they cannot run.
|
|
60
|
+
*/
|
|
61
|
+
function dogfoodLanePlan(framework, capabilities = {}, preferred) {
|
|
62
|
+
const options = dogfoodLaneOptions(framework, capabilities);
|
|
63
|
+
const supported = (lane) => !!lane && options.some((option) => option.lane === lane && option.supported);
|
|
64
|
+
const resolved = supported(preferred)
|
|
65
|
+
? preferred
|
|
66
|
+
: defaultDogfoodLane(framework, capabilities);
|
|
67
|
+
return {
|
|
68
|
+
preferred: resolved,
|
|
69
|
+
fallback: resolved !== 'browser' && supported('browser') ? 'browser' : undefined,
|
|
70
|
+
options,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
52
73
|
class DogfoodRuntimeError extends Error {
|
|
53
74
|
constructor(failure) {
|
|
54
75
|
super(failure.error);
|
|
@@ -173,8 +194,8 @@ class DogfoodController {
|
|
|
173
194
|
phase: 'preparing', attempt, project: this.project,
|
|
174
195
|
message: `Preparing ${this.project.name}…`, logs: [], startedAt: Date.now(),
|
|
175
196
|
});
|
|
176
|
-
const
|
|
177
|
-
project
|
|
197
|
+
const makeContext = (project) => ({
|
|
198
|
+
project,
|
|
178
199
|
attempt,
|
|
179
200
|
log: (line) => {
|
|
180
201
|
if (generation !== this.generation)
|
|
@@ -201,16 +222,69 @@ class DogfoodController {
|
|
|
201
222
|
this.cleanups.set(generation, owned);
|
|
202
223
|
},
|
|
203
224
|
isCurrent: () => generation === this.generation,
|
|
204
|
-
};
|
|
225
|
+
});
|
|
226
|
+
let activeProject = this.project;
|
|
227
|
+
let context = makeContext(activeProject);
|
|
205
228
|
try {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
229
|
+
const start = async () => {
|
|
230
|
+
await this.driver.prepare?.(context);
|
|
231
|
+
if (!context.isCurrent())
|
|
232
|
+
throw new DogfoodRuntimeError({
|
|
233
|
+
code: 'DOGFOOD_ATTEMPT_REPLACED', error: 'A newer Dogfood attempt replaced this one.',
|
|
234
|
+
remedy: 'Wait for the newer attempt.', retryable: true,
|
|
235
|
+
});
|
|
236
|
+
context.setPhase('starting', `Starting ${activeProject.name} on the ${activeProject.lane} lane…`);
|
|
237
|
+
return this.driver.start(context);
|
|
238
|
+
};
|
|
239
|
+
let result;
|
|
240
|
+
try {
|
|
241
|
+
result = await start();
|
|
242
|
+
}
|
|
243
|
+
catch (primaryError) {
|
|
244
|
+
const primaryFailure = failureFrom(primaryError);
|
|
245
|
+
const fallbackLane = activeProject.fallbackLane;
|
|
246
|
+
const mayFallback = !!fallbackLane
|
|
247
|
+
&& fallbackLane !== activeProject.lane
|
|
248
|
+
&& primaryFailure.retryable
|
|
249
|
+
&& primaryFailure.code !== 'DOGFOOD_ATTEMPT_REPLACED';
|
|
250
|
+
if (!mayFallback)
|
|
251
|
+
throw primaryError;
|
|
252
|
+
await this.runCleanups('all', generation);
|
|
253
|
+
const fallbackProject = {
|
|
254
|
+
...activeProject,
|
|
255
|
+
lane: fallbackLane,
|
|
256
|
+
fallbackLane: undefined,
|
|
257
|
+
};
|
|
258
|
+
const fallbackInvalid = validateDogfoodProject(fallbackProject);
|
|
259
|
+
if (fallbackInvalid)
|
|
260
|
+
throw primaryError;
|
|
261
|
+
const fallbackLine = {
|
|
262
|
+
text: `[fallback] ${activeProject.lane} failed (${primaryFailure.code}); trying ${fallbackLane}`,
|
|
263
|
+
at: Date.now(),
|
|
264
|
+
stream: 'system',
|
|
265
|
+
};
|
|
266
|
+
activeProject = fallbackProject;
|
|
267
|
+
this.replace({
|
|
268
|
+
...this.state,
|
|
269
|
+
project: activeProject,
|
|
270
|
+
phase: 'preparing',
|
|
271
|
+
message: `Recovering ${activeProject.name} in the ${fallbackLane} lane…`,
|
|
272
|
+
logs: [...this.state.logs, fallbackLine].slice(-this.maxLogLines),
|
|
273
|
+
lastOutputAt: fallbackLine.at,
|
|
274
|
+
failure: undefined,
|
|
211
275
|
});
|
|
212
|
-
|
|
213
|
-
|
|
276
|
+
context = makeContext(activeProject);
|
|
277
|
+
result = await start();
|
|
278
|
+
result = {
|
|
279
|
+
...result,
|
|
280
|
+
metadata: {
|
|
281
|
+
...result.metadata,
|
|
282
|
+
preferredLane: this.project.lane,
|
|
283
|
+
fallbackFrom: this.project.lane,
|
|
284
|
+
fallbackReason: primaryFailure.code,
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
}
|
|
214
288
|
if (!context.isCurrent()) {
|
|
215
289
|
await this.runCleanups('all', generation);
|
|
216
290
|
throw new DogfoodRuntimeError({
|
|
@@ -218,7 +292,7 @@ class DogfoodController {
|
|
|
218
292
|
remedy: 'Wait for the newer attempt.', retryable: true,
|
|
219
293
|
});
|
|
220
294
|
}
|
|
221
|
-
this.replace({ ...this.state, phase: 'ready', message: `${
|
|
295
|
+
this.replace({ ...this.state, project: activeProject, phase: 'ready', message: `${activeProject.name} is ready`, result, failure: undefined });
|
|
222
296
|
return result;
|
|
223
297
|
}
|
|
224
298
|
catch (error) {
|
|
@@ -32,6 +32,7 @@ export declare const DogfoodStatusRail: React.FC<{
|
|
|
32
32
|
export declare const DogfoodLanePicker: React.FC<{
|
|
33
33
|
options: readonly DogfoodLaneOption[];
|
|
34
34
|
selected: DogfoodLane;
|
|
35
|
+
fallbackLane?: DogfoodLane;
|
|
35
36
|
onSelect: (lane: DogfoodLane) => void;
|
|
36
37
|
colors?: Partial<DogfoodUiColors>;
|
|
37
38
|
showUnsupportedReasons?: boolean;
|
package/dist/DogfoodSessionUi.js
CHANGED
|
@@ -54,7 +54,7 @@ const DogfoodStatusRail = ({ steps, colors: colorOverrides }) => {
|
|
|
54
54
|
};
|
|
55
55
|
exports.DogfoodStatusRail = DogfoodStatusRail;
|
|
56
56
|
/** One lane selector and one default policy across Yaver, SFMG, and Talos. */
|
|
57
|
-
const DogfoodLanePicker = ({ options, selected, onSelect, colors: colorOverrides, showUnsupportedReasons = true }) => {
|
|
57
|
+
const DogfoodLanePicker = ({ options, selected, fallbackLane, onSelect, colors: colorOverrides, showUnsupportedReasons = true }) => {
|
|
58
58
|
const colors = resolvedColors(colorOverrides);
|
|
59
59
|
return (<react_native_1.View accessibilityRole="radiogroup" accessibilityLabel="Dogfood runtime lane">
|
|
60
60
|
<react_native_1.View style={styles.choiceRow}>
|
|
@@ -69,7 +69,8 @@ const DogfoodLanePicker = ({ options, selected, onSelect, colors: colorOverrides
|
|
|
69
69
|
},
|
|
70
70
|
]}>
|
|
71
71
|
<react_native_1.Text style={[styles.choiceText, { color: colors.text }, active && styles.choiceTextActive]}>
|
|
72
|
-
{option.label}
|
|
72
|
+
{option.label}
|
|
73
|
+
{active ? ' · preferred' : fallbackLane === option.lane ? ' · automatic fallback' : option.default ? ' · default' : ''}
|
|
73
74
|
</react_native_1.Text>
|
|
74
75
|
</react_native_1.Pressable>);
|
|
75
76
|
})}
|
package/dist/FeedbackModal.js
CHANGED
|
@@ -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:
|
|
324
|
-
|
|
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); },
|
|
@@ -533,7 +538,11 @@ const FeedbackModal = () => {
|
|
|
533
538
|
setShowVibeInput(authenticated || directDogfood);
|
|
534
539
|
setVibePrompt('');
|
|
535
540
|
if (onboarding) {
|
|
536
|
-
|
|
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.
|
|
545
|
+
setActiveTab('settings');
|
|
537
546
|
setDogfoodSetupStage('setup');
|
|
538
547
|
setDogfoodExpandedStep(null);
|
|
539
548
|
setDogfoodRuntime(null);
|
|
@@ -958,6 +967,10 @@ const FeedbackModal = () => {
|
|
|
958
967
|
nativeRuntimeAvailable: dogfoodNativeAvailable,
|
|
959
968
|
browserRuntimeAvailable: dogfoodBrowserAvailable,
|
|
960
969
|
});
|
|
970
|
+
const dogfoodLanePolicy = (0, DogfoodRuntime_1.dogfoodLanePlan)(dogfoodFramework, {
|
|
971
|
+
nativeRuntimeAvailable: dogfoodNativeAvailable,
|
|
972
|
+
browserRuntimeAvailable: dogfoodBrowserAvailable,
|
|
973
|
+
}, dogfoodLane);
|
|
961
974
|
const selectedDogfoodNativeTarget = dogfoodNativeTargets.find((target) => target.id === dogfoodNativeTargetId) || null;
|
|
962
975
|
const dogfoodLaneReady = dogfoodLaneChoices.some((option) => option.lane === dogfoodLane && option.supported)
|
|
963
976
|
&& (dogfoodLane !== 'webrtc' || !!selectedDogfoodNativeTarget?.enabled);
|
|
@@ -994,11 +1007,12 @@ const FeedbackModal = () => {
|
|
|
994
1007
|
},
|
|
995
1008
|
];
|
|
996
1009
|
const dogfoodStartBlocked = !dogfoodSetupReady || !dogfoodLaneReady;
|
|
997
|
-
const
|
|
1010
|
+
const activeDogfoodLane = dogfoodRuntime?.project.lane || dogfoodLane;
|
|
1011
|
+
const dogfoodSourceLabel = activeDogfoodLane === 'webrtc'
|
|
998
1012
|
? selectedDogfoodNativeTarget
|
|
999
1013
|
? [selectedDogfoodNativeTarget.label, selectedDogfoodNativeTarget.platform].filter(Boolean).join(' · ')
|
|
1000
1014
|
: 'Native simulator, emulator, or device'
|
|
1001
|
-
:
|
|
1015
|
+
: activeDogfoodLane === 'hermes'
|
|
1002
1016
|
? `Hermes build · ${machineCard.title}`
|
|
1003
1017
|
: `${dogfoodFramework === 'flutter' ? 'Flutter web compiler' : 'Metro / browser build'} · ${machineCard.title}`;
|
|
1004
1018
|
// Once the user fires off a vibe task, swap the entire modal body
|
|
@@ -1141,7 +1155,7 @@ const FeedbackModal = () => {
|
|
|
1141
1155
|
<react_native_1.Text style={styles.dogfoodSmallActionText}>Back</react_native_1.Text>
|
|
1142
1156
|
</react_native_1.Pressable>
|
|
1143
1157
|
</react_native_1.View>
|
|
1144
|
-
<DogfoodSessionUi_1.DogfoodLanePicker options={dogfoodLaneChoices} selected={dogfoodLane} colors={FeedbackModalTheme_1.FEEDBACK_DOGFOOD_LIGHT_COLORS} onSelect={(lane) => {
|
|
1158
|
+
<DogfoodSessionUi_1.DogfoodLanePicker options={dogfoodLaneChoices} selected={dogfoodLane} fallbackLane={dogfoodLanePolicy.fallback} colors={FeedbackModalTheme_1.FEEDBACK_DOGFOOD_LIGHT_COLORS} onSelect={(lane) => {
|
|
1145
1159
|
setDogfoodLane(lane);
|
|
1146
1160
|
const appId = YaverFeedback_1.YaverFeedback.getDogfoodOnboarding()?.appId;
|
|
1147
1161
|
if (appId)
|
|
@@ -78,6 +78,37 @@ describe('DogfoodController', () => {
|
|
|
78
78
|
await controller.stop();
|
|
79
79
|
expect(stopSecond).toHaveBeenCalledTimes(1);
|
|
80
80
|
});
|
|
81
|
+
test('keeps a failed preferred lane in the console and automatically recovers through browser', async () => {
|
|
82
|
+
const stopPreferred = jest.fn();
|
|
83
|
+
const lanes = [];
|
|
84
|
+
const controller = new DogfoodRuntime_1.DogfoodController({
|
|
85
|
+
...expo,
|
|
86
|
+
lane: 'hermes',
|
|
87
|
+
fallbackLane: 'browser',
|
|
88
|
+
}, {
|
|
89
|
+
async start(ctx) {
|
|
90
|
+
lanes.push(ctx.project.lane);
|
|
91
|
+
if (ctx.project.lane === 'hermes') {
|
|
92
|
+
ctx.registerCleanup(stopPreferred);
|
|
93
|
+
throw new DogfoodRuntime_1.DogfoodRuntimeError({
|
|
94
|
+
code: 'DOGFOOD_HERMES_BUILD_FAILED',
|
|
95
|
+
error: 'Hermes build failed',
|
|
96
|
+
remedy: 'Use the browser build.',
|
|
97
|
+
retryable: true,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return { lane: 'browser', url: 'http://agent/dev/', metadata: { recovered: true } };
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
await expect(controller.trigger()).resolves.toMatchObject({
|
|
104
|
+
lane: 'browser',
|
|
105
|
+
metadata: { fallbackFrom: 'hermes', fallbackReason: 'DOGFOOD_HERMES_BUILD_FAILED' },
|
|
106
|
+
});
|
|
107
|
+
expect(lanes).toEqual(['hermes', 'browser']);
|
|
108
|
+
expect(stopPreferred).toHaveBeenCalledTimes(1);
|
|
109
|
+
expect(controller.snapshot()).toMatchObject({ phase: 'ready', project: { lane: 'browser' } });
|
|
110
|
+
expect(controller.snapshot().logs.map((line) => line.text)).toContain('[fallback] hermes failed (DOGFOOD_HERMES_BUILD_FAILED); trying browser');
|
|
111
|
+
});
|
|
81
112
|
});
|
|
82
113
|
describe('Dogfood lanes and console events', () => {
|
|
83
114
|
test('Flutter is first-class on browser but cannot be mislabeled Hermes', () => {
|
|
@@ -95,6 +126,20 @@ describe('Dogfood lanes and console events', () => {
|
|
|
95
126
|
expect((0, DogfoodRuntime_1.defaultDogfoodLane)('flutter')).toBe('browser');
|
|
96
127
|
expect(flutter.find((option) => option.lane === 'hermes')?.supported).toBe(false);
|
|
97
128
|
});
|
|
129
|
+
test('uses browser as the framework-aware default and as second choice after an explicit native preference', () => {
|
|
130
|
+
expect((0, DogfoodRuntime_1.dogfoodLanePlan)('flutter', { nativeRuntimeAvailable: true })).toMatchObject({
|
|
131
|
+
preferred: 'browser', fallback: undefined,
|
|
132
|
+
});
|
|
133
|
+
expect((0, DogfoodRuntime_1.dogfoodLanePlan)('expo', { nativeRuntimeAvailable: true }, 'hermes')).toMatchObject({
|
|
134
|
+
preferred: 'hermes', fallback: 'browser',
|
|
135
|
+
});
|
|
136
|
+
expect((0, DogfoodRuntime_1.dogfoodLanePlan)('flutter', { nativeRuntimeAvailable: true }, 'webrtc')).toMatchObject({
|
|
137
|
+
preferred: 'webrtc', fallback: 'browser',
|
|
138
|
+
});
|
|
139
|
+
expect((0, DogfoodRuntime_1.dogfoodLanePlan)('swift', { nativeRuntimeAvailable: true }, 'webrtc')).toMatchObject({
|
|
140
|
+
preferred: 'webrtc', fallback: undefined,
|
|
141
|
+
});
|
|
142
|
+
});
|
|
98
143
|
test('keeps Yaver self-development on the same RN three-lane contract', () => {
|
|
99
144
|
const options = (0, DogfoodRuntime_1.dogfoodLaneOptions)('expo', { nativeRuntimeAvailable: true, selfDevelopment: true });
|
|
100
145
|
expect(options).toHaveLength(3);
|
|
@@ -39,11 +39,16 @@ describe('FeedbackModal authenticated chat contract', () => {
|
|
|
39
39
|
.toBeGreaterThanOrEqual(4.5);
|
|
40
40
|
});
|
|
41
41
|
it('uses Chat as the authenticated entry surface without legacy command buttons', () => {
|
|
42
|
-
expect(source).toContain("
|
|
42
|
+
expect(source).toContain("useState<'chat' | 'settings'>('chat')");
|
|
43
43
|
expect(source).toContain('setShowVibeInput(authenticated || directDogfood)');
|
|
44
44
|
expect(source).not.toContain('Screenshot & Fix');
|
|
45
45
|
expect(source).not.toContain('<DeployPanel');
|
|
46
46
|
});
|
|
47
|
+
it('opens explicit Dogfood onboarding on setup and makes the runtime console the first live surface', () => {
|
|
48
|
+
expect(source).toContain("setActiveTab('settings')");
|
|
49
|
+
expect(source).toContain("setDogfoodSetupStage('runtime')");
|
|
50
|
+
expect(source).toMatch(/dogfoodSetupStage === 'runtime'[\s\S]*?<DogfoodLiveConsole/);
|
|
51
|
+
});
|
|
47
52
|
it('keeps Dogfood setup to box, runner, and checkout before asking for a runtime', () => {
|
|
48
53
|
const setupSteps = source.match(/const dogfoodSetupSteps = \[([\s\S]*?)\n \];/)?.[1] || '';
|
|
49
54
|
expect([...setupSteps.matchAll(/key: '([^']+)'/g)].map((match) => match[1]))
|
|
@@ -56,7 +61,9 @@ describe('FeedbackModal authenticated chat contract', () => {
|
|
|
56
61
|
expect(source).toContain('label="Choose runtime"');
|
|
57
62
|
});
|
|
58
63
|
it('passes the selected native target and labels the live log source', () => {
|
|
59
|
-
expect(source).toContain("nativeTargetId:
|
|
64
|
+
expect(source).toContain("nativeTargetId: lanePlan.preferred === 'webrtc' ? dogfoodNativeTargetId : undefined");
|
|
65
|
+
expect(source).toContain('fallbackLane: lanePlan.fallback');
|
|
66
|
+
expect(source).toContain('fallbackLane={dogfoodLanePolicy.fallback}');
|
|
60
67
|
expect(source).toMatch(/<DogfoodLiveConsole[\s\S]*?sourceLabel=\{dogfoodSourceLabel\}/);
|
|
61
68
|
expect(source).toContain('Simulator, emulator, or device');
|
|
62
69
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -56,8 +56,8 @@ export { resolveSDKDogfood } from './dogfoodPolicy';
|
|
|
56
56
|
export type { DogfoodAccessSnapshot, DogfoodFlowSnapshot, SDKDogfoodConfig, SDKDogfoodStatus } from './dogfoodPolicy';
|
|
57
57
|
export { YaverDeviceDogfood } from './deviceDogfood';
|
|
58
58
|
export type { DeviceDogfoodOptions, DeviceDogfoodSession, DeviceDogfoodState } from './deviceDogfood';
|
|
59
|
-
export { DogfoodController, DogfoodRuntimeError, defaultDogfoodLane, dogfoodLaneOptions, dogfoodLogLinesFromDevEvent, runtimeLogLinesFromDevEvent, validateDogfoodProject, } from './DogfoodRuntime';
|
|
60
|
-
export type { DogfoodControllerOptions, DogfoodDriver, DogfoodFailure, DogfoodLane, DogfoodLaneOption, DogfoodLogLine, DogfoodPhase, DogfoodProject, DogfoodResult, DogfoodRunContext, DogfoodSnapshot, } from './DogfoodRuntime';
|
|
59
|
+
export { DogfoodController, DogfoodRuntimeError, defaultDogfoodLane, dogfoodLanePlan, dogfoodLaneOptions, dogfoodLogLinesFromDevEvent, runtimeLogLinesFromDevEvent, validateDogfoodProject, } from './DogfoodRuntime';
|
|
60
|
+
export type { DogfoodControllerOptions, DogfoodDriver, DogfoodFailure, DogfoodLane, DogfoodLaneOption, DogfoodLanePlan, DogfoodLogLine, DogfoodPhase, DogfoodProject, DogfoodResult, DogfoodRunContext, DogfoodSnapshot, } from './DogfoodRuntime';
|
|
61
61
|
export { DogfoodLanePicker, DogfoodLiveConsole, DogfoodStatusRail } from './DogfoodSessionUi';
|
|
62
62
|
export type { DogfoodStatusStep, DogfoodStatusTone, DogfoodUiColors, } from './DogfoodSessionUi';
|
|
63
63
|
export { FeedbackModal } from './FeedbackModal';
|
package/dist/index.js
CHANGED
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
* ```
|
|
29
29
|
*/
|
|
30
30
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
-
exports.
|
|
32
|
-
exports.uploadFeedback = exports.isVideoRecording = exports.stopVideoRecording = exports.startVideoRecording = exports.pickFeedbackFile = exports.captureScreenshot = exports.DEFAULT_OAUTH_REDIRECT = exports.DEFAULT_WEB_BASE_URL = exports.DEFAULT_CONVEX_SITE_URL = exports.listReachableDevices = exports.loginWithEmail = exports.signupWithEmail = exports.signInWithOAuth = exports.signInWithApple = exports.validateToken = exports.clearSelectedDeviceId = exports.saveSelectedDeviceId = exports.getDogfoodAccountAccess = exports.getSelectedDeviceId = exports.saveUser = exports.getUser = exports.clearToken = exports.saveToken = void 0;
|
|
31
|
+
exports.getWebBaseUrl = exports.getConvexSiteUrl = exports.configureAuthEndpoints = exports.setPreferredDogfoodLane = exports.getPreferredDogfoodLane = exports.clearQuickIconDisabled = exports.setQuickIconDisabled = exports.getQuickIconDisabled = exports.FixReport = exports.QuickActionIcon = exports.DogfoodQuickControls = exports.FeedbackModal = exports.DogfoodStatusRail = exports.DogfoodLiveConsole = exports.DogfoodLanePicker = exports.validateDogfoodProject = exports.runtimeLogLinesFromDevEvent = exports.dogfoodLogLinesFromDevEvent = exports.dogfoodLaneOptions = exports.dogfoodLanePlan = exports.defaultDogfoodLane = exports.DogfoodRuntimeError = exports.DogfoodController = exports.YaverDeviceDogfood = exports.resolveSDKDogfood = exports.isYaverModeBadgeHidden = exports.showYaverModeBadge = exports.hideYaverModeBadge = exports.YaverModeBadge = exports.FloatingButton = exports.ShakeDetector = exports.AuthOverlay = exports.PairDeviceModal = exports.YaverMachinePickerScreen = exports.YaverLoginScreen = exports.YaverConnectionScreen = exports.RELOAD_APP_PATH = exports.RELOAD_PATH = exports.describeReloadFailure = exports.reloadFrameworkFamily = exports.reloadRequest = exports.reloadActions = exports.createP2PDogfoodDriver = exports.P2PClient = exports.YaverDiscovery = exports.initExpo = exports.YaverUpdates = exports.BlackBox = exports.captureStoreScreenshots = exports.YaverFeedback = void 0;
|
|
32
|
+
exports.uploadFeedback = exports.isVideoRecording = exports.stopVideoRecording = exports.startVideoRecording = exports.pickFeedbackFile = exports.captureScreenshot = exports.DEFAULT_OAUTH_REDIRECT = exports.DEFAULT_WEB_BASE_URL = exports.DEFAULT_CONVEX_SITE_URL = exports.listReachableDevices = exports.loginWithEmail = exports.signupWithEmail = exports.signInWithOAuth = exports.signInWithApple = exports.validateToken = exports.clearSelectedDeviceId = exports.saveSelectedDeviceId = exports.getDogfoodAccountAccess = exports.getSelectedDeviceId = exports.saveUser = exports.getUser = exports.clearToken = exports.saveToken = exports.getToken = void 0;
|
|
33
33
|
var YaverFeedback_1 = require("./YaverFeedback");
|
|
34
34
|
Object.defineProperty(exports, "YaverFeedback", { enumerable: true, get: function () { return YaverFeedback_1.YaverFeedback; } });
|
|
35
35
|
var storeShots_1 = require("./storeShots");
|
|
@@ -83,6 +83,7 @@ var DogfoodRuntime_1 = require("./DogfoodRuntime");
|
|
|
83
83
|
Object.defineProperty(exports, "DogfoodController", { enumerable: true, get: function () { return DogfoodRuntime_1.DogfoodController; } });
|
|
84
84
|
Object.defineProperty(exports, "DogfoodRuntimeError", { enumerable: true, get: function () { return DogfoodRuntime_1.DogfoodRuntimeError; } });
|
|
85
85
|
Object.defineProperty(exports, "defaultDogfoodLane", { enumerable: true, get: function () { return DogfoodRuntime_1.defaultDogfoodLane; } });
|
|
86
|
+
Object.defineProperty(exports, "dogfoodLanePlan", { enumerable: true, get: function () { return DogfoodRuntime_1.dogfoodLanePlan; } });
|
|
86
87
|
Object.defineProperty(exports, "dogfoodLaneOptions", { enumerable: true, get: function () { return DogfoodRuntime_1.dogfoodLaneOptions; } });
|
|
87
88
|
Object.defineProperty(exports, "dogfoodLogLinesFromDevEvent", { enumerable: true, get: function () { return DogfoodRuntime_1.dogfoodLogLinesFromDevEvent; } });
|
|
88
89
|
Object.defineProperty(exports, "runtimeLogLinesFromDevEvent", { enumerable: true, get: function () { return DogfoodRuntime_1.runtimeLogLinesFromDevEvent; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yaver-feedback-react-native",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.11",
|
|
4
4
|
"description": "Visual feedback SDK for Yaver — bug reports, screen recording, voice vibe coding, and local-first developer workflows",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
package/src/DogfoodRuntime.ts
CHANGED
|
@@ -27,6 +27,13 @@ export interface DogfoodProject {
|
|
|
27
27
|
workDir: string;
|
|
28
28
|
framework: string;
|
|
29
29
|
lane: DogfoodLane;
|
|
30
|
+
/**
|
|
31
|
+
* Optional automatic recovery lane. The shared onboarding flow uses the
|
|
32
|
+
* browser lane here when a user prefers Hermes or WebRTC for a project that
|
|
33
|
+
* can also render in a browser. The failed preferred attempt remains in the
|
|
34
|
+
* live console; the fallback is never silent.
|
|
35
|
+
*/
|
|
36
|
+
fallbackLane?: DogfoodLane;
|
|
30
37
|
/** Optional source URL for drivers that can clone missing source. */
|
|
31
38
|
repositoryUrl?: string;
|
|
32
39
|
/** Optional native target from /remote-runtime/capabilities for WebRTC. */
|
|
@@ -41,6 +48,13 @@ export interface DogfoodLaneOption {
|
|
|
41
48
|
reason?: string;
|
|
42
49
|
}
|
|
43
50
|
|
|
51
|
+
export interface DogfoodLanePlan {
|
|
52
|
+
preferred: DogfoodLane;
|
|
53
|
+
/** Browser is the cheap, portable recovery lane for RN/Expo/Flutter/web. */
|
|
54
|
+
fallback?: DogfoodLane;
|
|
55
|
+
options: DogfoodLaneOption[];
|
|
56
|
+
}
|
|
57
|
+
|
|
44
58
|
/** One framework-to-lane matrix for Yaver and third-party consumers. */
|
|
45
59
|
export function dogfoodLaneOptions(
|
|
46
60
|
framework: string,
|
|
@@ -89,6 +103,36 @@ export function defaultDogfoodLane(
|
|
|
89
103
|
|| 'browser';
|
|
90
104
|
}
|
|
91
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Resolve one ordered lane policy for Yaver and embedded SDK hosts.
|
|
108
|
+
*
|
|
109
|
+
* Browser is the onboarding default for browser-capable React Native, Expo,
|
|
110
|
+
* Flutter, and web projects. When the user explicitly prefers Hermes or
|
|
111
|
+
* WebRTC, browser becomes the automatic second attempt. Native-only projects
|
|
112
|
+
* remain WebRTC-only and never advertise a browser recovery they cannot run.
|
|
113
|
+
*/
|
|
114
|
+
export function dogfoodLanePlan(
|
|
115
|
+
framework: string,
|
|
116
|
+
capabilities: {
|
|
117
|
+
nativeRuntimeAvailable?: boolean;
|
|
118
|
+
browserRuntimeAvailable?: boolean;
|
|
119
|
+
selfDevelopment?: boolean;
|
|
120
|
+
} = {},
|
|
121
|
+
preferred?: DogfoodLane | null,
|
|
122
|
+
): DogfoodLanePlan {
|
|
123
|
+
const options = dogfoodLaneOptions(framework, capabilities);
|
|
124
|
+
const supported = (lane: DogfoodLane | null | undefined) =>
|
|
125
|
+
!!lane && options.some((option) => option.lane === lane && option.supported);
|
|
126
|
+
const resolved = supported(preferred)
|
|
127
|
+
? preferred!
|
|
128
|
+
: defaultDogfoodLane(framework, capabilities);
|
|
129
|
+
return {
|
|
130
|
+
preferred: resolved,
|
|
131
|
+
fallback: resolved !== 'browser' && supported('browser') ? 'browser' : undefined,
|
|
132
|
+
options,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
92
136
|
export interface DogfoodLogLine {
|
|
93
137
|
text: string;
|
|
94
138
|
at: number;
|
|
@@ -291,8 +335,8 @@ export class DogfoodController {
|
|
|
291
335
|
message: `Preparing ${this.project.name}…`, logs: [], startedAt: Date.now(),
|
|
292
336
|
});
|
|
293
337
|
|
|
294
|
-
const
|
|
295
|
-
project
|
|
338
|
+
const makeContext = (project: DogfoodProject): DogfoodRunContext => ({
|
|
339
|
+
project,
|
|
296
340
|
attempt,
|
|
297
341
|
log: (line) => {
|
|
298
342
|
if (generation !== this.generation) return;
|
|
@@ -316,16 +360,69 @@ export class DogfoodController {
|
|
|
316
360
|
this.cleanups.set(generation, owned);
|
|
317
361
|
},
|
|
318
362
|
isCurrent: () => generation === this.generation,
|
|
319
|
-
};
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
let activeProject = this.project;
|
|
366
|
+
let context = makeContext(activeProject);
|
|
320
367
|
|
|
321
368
|
try {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
369
|
+
const start = async (): Promise<DogfoodResult> => {
|
|
370
|
+
await this.driver.prepare?.(context);
|
|
371
|
+
if (!context.isCurrent()) throw new DogfoodRuntimeError({
|
|
372
|
+
code: 'DOGFOOD_ATTEMPT_REPLACED', error: 'A newer Dogfood attempt replaced this one.',
|
|
373
|
+
remedy: 'Wait for the newer attempt.', retryable: true,
|
|
374
|
+
});
|
|
375
|
+
context.setPhase('starting', `Starting ${activeProject.name} on the ${activeProject.lane} lane…`);
|
|
376
|
+
return this.driver.start(context);
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
let result: DogfoodResult;
|
|
380
|
+
try {
|
|
381
|
+
result = await start();
|
|
382
|
+
} catch (primaryError) {
|
|
383
|
+
const primaryFailure = failureFrom(primaryError);
|
|
384
|
+
const fallbackLane = activeProject.fallbackLane;
|
|
385
|
+
const mayFallback = !!fallbackLane
|
|
386
|
+
&& fallbackLane !== activeProject.lane
|
|
387
|
+
&& primaryFailure.retryable
|
|
388
|
+
&& primaryFailure.code !== 'DOGFOOD_ATTEMPT_REPLACED';
|
|
389
|
+
if (!mayFallback) throw primaryError;
|
|
390
|
+
|
|
391
|
+
await this.runCleanups('all', generation);
|
|
392
|
+
const fallbackProject: DogfoodProject = {
|
|
393
|
+
...activeProject,
|
|
394
|
+
lane: fallbackLane!,
|
|
395
|
+
fallbackLane: undefined,
|
|
396
|
+
};
|
|
397
|
+
const fallbackInvalid = validateDogfoodProject(fallbackProject);
|
|
398
|
+
if (fallbackInvalid) throw primaryError;
|
|
399
|
+
const fallbackLine: DogfoodLogLine = {
|
|
400
|
+
text: `[fallback] ${activeProject.lane} failed (${primaryFailure.code}); trying ${fallbackLane}`,
|
|
401
|
+
at: Date.now(),
|
|
402
|
+
stream: 'system',
|
|
403
|
+
};
|
|
404
|
+
activeProject = fallbackProject;
|
|
405
|
+
this.replace({
|
|
406
|
+
...this.state,
|
|
407
|
+
project: activeProject,
|
|
408
|
+
phase: 'preparing',
|
|
409
|
+
message: `Recovering ${activeProject.name} in the ${fallbackLane} lane…`,
|
|
410
|
+
logs: [...this.state.logs, fallbackLine].slice(-this.maxLogLines),
|
|
411
|
+
lastOutputAt: fallbackLine.at,
|
|
412
|
+
failure: undefined,
|
|
413
|
+
});
|
|
414
|
+
context = makeContext(activeProject);
|
|
415
|
+
result = await start();
|
|
416
|
+
result = {
|
|
417
|
+
...result,
|
|
418
|
+
metadata: {
|
|
419
|
+
...result.metadata,
|
|
420
|
+
preferredLane: this.project.lane,
|
|
421
|
+
fallbackFrom: this.project.lane,
|
|
422
|
+
fallbackReason: primaryFailure.code,
|
|
423
|
+
},
|
|
424
|
+
};
|
|
425
|
+
}
|
|
329
426
|
if (!context.isCurrent()) {
|
|
330
427
|
await this.runCleanups('all', generation);
|
|
331
428
|
throw new DogfoodRuntimeError({
|
|
@@ -333,7 +430,7 @@ export class DogfoodController {
|
|
|
333
430
|
remedy: 'Wait for the newer attempt.', retryable: true,
|
|
334
431
|
});
|
|
335
432
|
}
|
|
336
|
-
this.replace({ ...this.state, phase: 'ready', message: `${
|
|
433
|
+
this.replace({ ...this.state, project: activeProject, phase: 'ready', message: `${activeProject.name} is ready`, result, failure: undefined });
|
|
337
434
|
return result;
|
|
338
435
|
} catch (error) {
|
|
339
436
|
const failure = failureFrom(error);
|
package/src/DogfoodSessionUi.tsx
CHANGED
|
@@ -101,10 +101,11 @@ export const DogfoodStatusRail: React.FC<{
|
|
|
101
101
|
export const DogfoodLanePicker: React.FC<{
|
|
102
102
|
options: readonly DogfoodLaneOption[];
|
|
103
103
|
selected: DogfoodLane;
|
|
104
|
+
fallbackLane?: DogfoodLane;
|
|
104
105
|
onSelect: (lane: DogfoodLane) => void;
|
|
105
106
|
colors?: Partial<DogfoodUiColors>;
|
|
106
107
|
showUnsupportedReasons?: boolean;
|
|
107
|
-
}> = ({ options, selected, onSelect, colors: colorOverrides, showUnsupportedReasons = true }) => {
|
|
108
|
+
}> = ({ options, selected, fallbackLane, onSelect, colors: colorOverrides, showUnsupportedReasons = true }) => {
|
|
108
109
|
const colors = resolvedColors(colorOverrides);
|
|
109
110
|
return (
|
|
110
111
|
<View accessibilityRole="radiogroup" accessibilityLabel="Dogfood runtime lane">
|
|
@@ -128,7 +129,8 @@ export const DogfoodLanePicker: React.FC<{
|
|
|
128
129
|
]}
|
|
129
130
|
>
|
|
130
131
|
<Text style={[styles.choiceText, { color: colors.text }, active && styles.choiceTextActive]}>
|
|
131
|
-
{option.label}
|
|
132
|
+
{option.label}
|
|
133
|
+
{active ? ' · preferred' : fallbackLane === option.lane ? ' · automatic fallback' : option.default ? ' · default' : ''}
|
|
132
134
|
</Text>
|
|
133
135
|
</Pressable>
|
|
134
136
|
);
|
package/src/FeedbackModal.tsx
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
import {
|
|
41
41
|
DogfoodController,
|
|
42
42
|
defaultDogfoodLane,
|
|
43
|
+
dogfoodLanePlan,
|
|
43
44
|
dogfoodLaneOptions,
|
|
44
45
|
type DogfoodLane,
|
|
45
46
|
type DogfoodSnapshot,
|
|
@@ -389,12 +390,17 @@ export const FeedbackModal: React.FC = () => {
|
|
|
389
390
|
if (!client || !dogfoodProject || !onboarding) return;
|
|
390
391
|
await dogfoodControllerRef.current?.stop().catch(() => {});
|
|
391
392
|
const framework = dogfoodProject.framework || onboarding.framework || 'expo';
|
|
393
|
+
const lanePlan = dogfoodLanePlan(framework, {
|
|
394
|
+
nativeRuntimeAvailable: dogfoodNativeAvailable,
|
|
395
|
+
browserRuntimeAvailable: dogfoodBrowserAvailable,
|
|
396
|
+
}, dogfoodLane);
|
|
392
397
|
const controller = new DogfoodController({
|
|
393
398
|
name: dogfoodProject.name,
|
|
394
399
|
workDir: dogfoodProject.path,
|
|
395
400
|
framework,
|
|
396
|
-
lane:
|
|
397
|
-
|
|
401
|
+
lane: lanePlan.preferred,
|
|
402
|
+
fallbackLane: lanePlan.fallback,
|
|
403
|
+
nativeTargetId: lanePlan.preferred === 'webrtc' ? dogfoodNativeTargetId : undefined,
|
|
398
404
|
}, createP2PDogfoodDriver(client), {
|
|
399
405
|
onChange: (snapshot) => { if (mountedRef.current) setDogfoodRuntime(snapshot); },
|
|
400
406
|
});
|
|
@@ -608,7 +614,11 @@ export const FeedbackModal: React.FC = () => {
|
|
|
608
614
|
setShowVibeInput(authenticated || directDogfood);
|
|
609
615
|
setVibePrompt('');
|
|
610
616
|
if (onboarding) {
|
|
611
|
-
|
|
617
|
+
// A Dogfood shortcut is an explicit setup/runtime intent. Opening on
|
|
618
|
+
// Chat hid the machine/runner/checkout gate for signed-in SFMG users;
|
|
619
|
+
// keep the SDK-owned Dogfood surface visible, then show its live logs
|
|
620
|
+
// immediately when Start is tapped.
|
|
621
|
+
setActiveTab('settings');
|
|
612
622
|
setDogfoodSetupStage('setup');
|
|
613
623
|
setDogfoodExpandedStep(null);
|
|
614
624
|
setDogfoodRuntime(null);
|
|
@@ -1056,6 +1066,10 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1056
1066
|
nativeRuntimeAvailable: dogfoodNativeAvailable,
|
|
1057
1067
|
browserRuntimeAvailable: dogfoodBrowserAvailable,
|
|
1058
1068
|
});
|
|
1069
|
+
const dogfoodLanePolicy = dogfoodLanePlan(dogfoodFramework, {
|
|
1070
|
+
nativeRuntimeAvailable: dogfoodNativeAvailable,
|
|
1071
|
+
browserRuntimeAvailable: dogfoodBrowserAvailable,
|
|
1072
|
+
}, dogfoodLane);
|
|
1059
1073
|
const selectedDogfoodNativeTarget = dogfoodNativeTargets.find((target) => target.id === dogfoodNativeTargetId) || null;
|
|
1060
1074
|
const dogfoodLaneReady = dogfoodLaneChoices.some((option) => option.lane === dogfoodLane && option.supported)
|
|
1061
1075
|
&& (dogfoodLane !== 'webrtc' || !!selectedDogfoodNativeTarget?.enabled);
|
|
@@ -1092,11 +1106,12 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1092
1106
|
},
|
|
1093
1107
|
];
|
|
1094
1108
|
const dogfoodStartBlocked = !dogfoodSetupReady || !dogfoodLaneReady;
|
|
1095
|
-
const
|
|
1109
|
+
const activeDogfoodLane = dogfoodRuntime?.project.lane || dogfoodLane;
|
|
1110
|
+
const dogfoodSourceLabel = activeDogfoodLane === 'webrtc'
|
|
1096
1111
|
? selectedDogfoodNativeTarget
|
|
1097
1112
|
? [selectedDogfoodNativeTarget.label, selectedDogfoodNativeTarget.platform].filter(Boolean).join(' · ')
|
|
1098
1113
|
: 'Native simulator, emulator, or device'
|
|
1099
|
-
:
|
|
1114
|
+
: activeDogfoodLane === 'hermes'
|
|
1100
1115
|
? `Hermes build · ${machineCard.title}`
|
|
1101
1116
|
: `${dogfoodFramework === 'flutter' ? 'Flutter web compiler' : 'Metro / browser build'} · ${machineCard.title}`;
|
|
1102
1117
|
|
|
@@ -1347,6 +1362,7 @@ export const FeedbackModal: React.FC = () => {
|
|
|
1347
1362
|
<DogfoodLanePicker
|
|
1348
1363
|
options={dogfoodLaneChoices}
|
|
1349
1364
|
selected={dogfoodLane}
|
|
1365
|
+
fallbackLane={dogfoodLanePolicy.fallback}
|
|
1350
1366
|
colors={FEEDBACK_DOGFOOD_LIGHT_COLORS}
|
|
1351
1367
|
onSelect={(lane) => {
|
|
1352
1368
|
setDogfoodLane(lane);
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
DogfoodController,
|
|
3
3
|
DogfoodRuntimeError,
|
|
4
4
|
defaultDogfoodLane,
|
|
5
|
+
dogfoodLanePlan,
|
|
5
6
|
dogfoodLaneOptions,
|
|
6
7
|
dogfoodLogLinesFromDevEvent,
|
|
7
8
|
validateDogfoodProject,
|
|
@@ -92,6 +93,41 @@ describe('DogfoodController', () => {
|
|
|
92
93
|
await controller.stop();
|
|
93
94
|
expect(stopSecond).toHaveBeenCalledTimes(1);
|
|
94
95
|
});
|
|
96
|
+
|
|
97
|
+
test('keeps a failed preferred lane in the console and automatically recovers through browser', async () => {
|
|
98
|
+
const stopPreferred = jest.fn();
|
|
99
|
+
const lanes: string[] = [];
|
|
100
|
+
const controller = new DogfoodController({
|
|
101
|
+
...expo,
|
|
102
|
+
lane: 'hermes',
|
|
103
|
+
fallbackLane: 'browser',
|
|
104
|
+
}, {
|
|
105
|
+
async start(ctx) {
|
|
106
|
+
lanes.push(ctx.project.lane);
|
|
107
|
+
if (ctx.project.lane === 'hermes') {
|
|
108
|
+
ctx.registerCleanup(stopPreferred);
|
|
109
|
+
throw new DogfoodRuntimeError({
|
|
110
|
+
code: 'DOGFOOD_HERMES_BUILD_FAILED',
|
|
111
|
+
error: 'Hermes build failed',
|
|
112
|
+
remedy: 'Use the browser build.',
|
|
113
|
+
retryable: true,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return { lane: 'browser', url: 'http://agent/dev/', metadata: { recovered: true } };
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
await expect(controller.trigger()).resolves.toMatchObject({
|
|
121
|
+
lane: 'browser',
|
|
122
|
+
metadata: { fallbackFrom: 'hermes', fallbackReason: 'DOGFOOD_HERMES_BUILD_FAILED' },
|
|
123
|
+
});
|
|
124
|
+
expect(lanes).toEqual(['hermes', 'browser']);
|
|
125
|
+
expect(stopPreferred).toHaveBeenCalledTimes(1);
|
|
126
|
+
expect(controller.snapshot()).toMatchObject({ phase: 'ready', project: { lane: 'browser' } });
|
|
127
|
+
expect(controller.snapshot().logs.map((line) => line.text)).toContain(
|
|
128
|
+
'[fallback] hermes failed (DOGFOOD_HERMES_BUILD_FAILED); trying browser',
|
|
129
|
+
);
|
|
130
|
+
});
|
|
95
131
|
});
|
|
96
132
|
|
|
97
133
|
describe('Dogfood lanes and console events', () => {
|
|
@@ -112,6 +148,21 @@ describe('Dogfood lanes and console events', () => {
|
|
|
112
148
|
expect(flutter.find((option) => option.lane === 'hermes')?.supported).toBe(false);
|
|
113
149
|
});
|
|
114
150
|
|
|
151
|
+
test('uses browser as the framework-aware default and as second choice after an explicit native preference', () => {
|
|
152
|
+
expect(dogfoodLanePlan('flutter', { nativeRuntimeAvailable: true })).toMatchObject({
|
|
153
|
+
preferred: 'browser', fallback: undefined,
|
|
154
|
+
});
|
|
155
|
+
expect(dogfoodLanePlan('expo', { nativeRuntimeAvailable: true }, 'hermes')).toMatchObject({
|
|
156
|
+
preferred: 'hermes', fallback: 'browser',
|
|
157
|
+
});
|
|
158
|
+
expect(dogfoodLanePlan('flutter', { nativeRuntimeAvailable: true }, 'webrtc')).toMatchObject({
|
|
159
|
+
preferred: 'webrtc', fallback: 'browser',
|
|
160
|
+
});
|
|
161
|
+
expect(dogfoodLanePlan('swift', { nativeRuntimeAvailable: true }, 'webrtc')).toMatchObject({
|
|
162
|
+
preferred: 'webrtc', fallback: undefined,
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
115
166
|
test('keeps Yaver self-development on the same RN three-lane contract', () => {
|
|
116
167
|
const options = dogfoodLaneOptions('expo', { nativeRuntimeAvailable: true, selfDevelopment: true });
|
|
117
168
|
expect(options).toHaveLength(3);
|
|
@@ -46,12 +46,18 @@ describe('FeedbackModal authenticated chat contract', () => {
|
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
it('uses Chat as the authenticated entry surface without legacy command buttons', () => {
|
|
49
|
-
expect(source).toContain("
|
|
49
|
+
expect(source).toContain("useState<'chat' | 'settings'>('chat')");
|
|
50
50
|
expect(source).toContain('setShowVibeInput(authenticated || directDogfood)');
|
|
51
51
|
expect(source).not.toContain('Screenshot & Fix');
|
|
52
52
|
expect(source).not.toContain('<DeployPanel');
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
+
it('opens explicit Dogfood onboarding on setup and makes the runtime console the first live surface', () => {
|
|
56
|
+
expect(source).toContain("setActiveTab('settings')");
|
|
57
|
+
expect(source).toContain("setDogfoodSetupStage('runtime')");
|
|
58
|
+
expect(source).toMatch(/dogfoodSetupStage === 'runtime'[\s\S]*?<DogfoodLiveConsole/);
|
|
59
|
+
});
|
|
60
|
+
|
|
55
61
|
it('keeps Dogfood setup to box, runner, and checkout before asking for a runtime', () => {
|
|
56
62
|
const setupSteps = source.match(/const dogfoodSetupSteps = \[([\s\S]*?)\n \];/)?.[1] || '';
|
|
57
63
|
expect([...setupSteps.matchAll(/key: '([^']+)'/g)].map((match) => match[1]))
|
|
@@ -65,7 +71,9 @@ describe('FeedbackModal authenticated chat contract', () => {
|
|
|
65
71
|
});
|
|
66
72
|
|
|
67
73
|
it('passes the selected native target and labels the live log source', () => {
|
|
68
|
-
expect(source).toContain("nativeTargetId:
|
|
74
|
+
expect(source).toContain("nativeTargetId: lanePlan.preferred === 'webrtc' ? dogfoodNativeTargetId : undefined");
|
|
75
|
+
expect(source).toContain('fallbackLane: lanePlan.fallback');
|
|
76
|
+
expect(source).toContain('fallbackLane={dogfoodLanePolicy.fallback}');
|
|
69
77
|
expect(source).toMatch(/<DogfoodLiveConsole[\s\S]*?sourceLabel=\{dogfoodSourceLabel\}/);
|
|
70
78
|
expect(source).toContain('Simulator, emulator, or device');
|
|
71
79
|
});
|
package/src/index.ts
CHANGED
|
@@ -92,6 +92,7 @@ export {
|
|
|
92
92
|
DogfoodController,
|
|
93
93
|
DogfoodRuntimeError,
|
|
94
94
|
defaultDogfoodLane,
|
|
95
|
+
dogfoodLanePlan,
|
|
95
96
|
dogfoodLaneOptions,
|
|
96
97
|
dogfoodLogLinesFromDevEvent,
|
|
97
98
|
runtimeLogLinesFromDevEvent,
|
|
@@ -103,6 +104,7 @@ export type {
|
|
|
103
104
|
DogfoodFailure,
|
|
104
105
|
DogfoodLane,
|
|
105
106
|
DogfoodLaneOption,
|
|
107
|
+
DogfoodLanePlan,
|
|
106
108
|
DogfoodLogLine,
|
|
107
109
|
DogfoodPhase,
|
|
108
110
|
DogfoodProject,
|