yaver-feedback-react-native 0.9.9 → 0.9.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app.plugin.js +24 -10
- package/dist/DogfoodRuntime.d.ts +32 -1
- package/dist/DogfoodRuntime.js +94 -14
- package/dist/DogfoodSessionUi.d.ts +2 -0
- package/dist/DogfoodSessionUi.js +8 -4
- package/dist/FeedbackModal.js +263 -278
- package/dist/FeedbackModalTheme.d.ts +5 -0
- package/dist/FeedbackModalTheme.js +29 -0
- package/dist/P2PClient.d.ts +2 -0
- package/dist/P2PClient.js +1 -1
- package/dist/P2PDogfoodDriver.js +17 -0
- package/dist/__tests__/DogfoodRuntime.test.js +59 -0
- package/dist/__tests__/FeedbackModalContract.test.d.ts +1 -0
- package/dist/__tests__/FeedbackModalContract.test.js +77 -0
- package/dist/__tests__/NativeDogfoodShortcut.test.js +23 -0
- package/dist/__tests__/P2PDogfoodDriver.test.js +4 -1
- package/dist/index.d.ts +6 -7
- package/dist/index.js +7 -7
- package/ios/YaverHotReload.swift +3 -4
- package/package.json +1 -1
- package/src/DogfoodRuntime.ts +129 -15
- package/src/DogfoodSessionUi.tsx +10 -4
- package/src/FeedbackModal.tsx +335 -372
- package/src/FeedbackModalTheme.ts +29 -0
- package/src/P2PClient.ts +3 -1
- package/src/P2PDogfoodDriver.ts +17 -0
- package/src/__tests__/DogfoodRuntime.test.ts +66 -0
- package/src/__tests__/FeedbackModalContract.test.ts +88 -0
- package/src/__tests__/NativeDogfoodShortcut.test.ts +25 -0
- package/src/__tests__/P2PDogfoodDriver.test.ts +4 -0
- package/src/index.ts +6 -5
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DogfoodUiColors } from './DogfoodSessionUi';
|
|
2
|
+
/** High-contrast colors for shared Dogfood controls inside the light SDK sheet. */
|
|
3
|
+
export declare const FEEDBACK_DOGFOOD_LIGHT_COLORS: DogfoodUiColors;
|
|
4
|
+
/** The log surface stays dark, but its foregrounds are explicit and readable. */
|
|
5
|
+
export declare const FEEDBACK_DOGFOOD_CONSOLE_COLORS: DogfoodUiColors;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FEEDBACK_DOGFOOD_CONSOLE_COLORS = exports.FEEDBACK_DOGFOOD_LIGHT_COLORS = void 0;
|
|
4
|
+
/** High-contrast colors for shared Dogfood controls inside the light SDK sheet. */
|
|
5
|
+
exports.FEEDBACK_DOGFOOD_LIGHT_COLORS = {
|
|
6
|
+
background: '#ffffff',
|
|
7
|
+
border: '#c9c9d5',
|
|
8
|
+
text: '#222229',
|
|
9
|
+
muted: '#5f5f6b',
|
|
10
|
+
accent: '#5645d8',
|
|
11
|
+
accentSoft: '#e7e5ff',
|
|
12
|
+
ready: '#137a3f',
|
|
13
|
+
attention: '#9a5700',
|
|
14
|
+
blocked: '#b42318',
|
|
15
|
+
console: '#ffffff',
|
|
16
|
+
};
|
|
17
|
+
/** The log surface stays dark, but its foregrounds are explicit and readable. */
|
|
18
|
+
exports.FEEDBACK_DOGFOOD_CONSOLE_COLORS = {
|
|
19
|
+
...exports.FEEDBACK_DOGFOOD_LIGHT_COLORS,
|
|
20
|
+
background: '#15151b',
|
|
21
|
+
border: '#454552',
|
|
22
|
+
text: '#f8fafc',
|
|
23
|
+
muted: '#cbd5e1',
|
|
24
|
+
accent: '#a5b4fc',
|
|
25
|
+
ready: '#4ade80',
|
|
26
|
+
attention: '#fbbf24',
|
|
27
|
+
blocked: '#fca5a5',
|
|
28
|
+
console: '#15151b',
|
|
29
|
+
};
|
package/dist/P2PClient.d.ts
CHANGED
|
@@ -178,6 +178,8 @@ export declare class P2PClient {
|
|
|
178
178
|
framework?: string;
|
|
179
179
|
frameworks?: string[];
|
|
180
180
|
surfaces?: string[];
|
|
181
|
+
branch?: string;
|
|
182
|
+
gitRemote?: string;
|
|
181
183
|
}>>;
|
|
182
184
|
getOpenCodeConfig(): Promise<OpenCodeConfigSummary | null>;
|
|
183
185
|
saveOpenCodeConfig(patch: {
|
package/dist/P2PClient.js
CHANGED
|
@@ -259,7 +259,7 @@ function friendlyReloadError(status, body) {
|
|
|
259
259
|
const hasLoopback = lower.indexOf('127.0.0.1') >= 0 || lower.indexOf('localhost') >= 0;
|
|
260
260
|
if (hasRefused && hasLoopback) {
|
|
261
261
|
return ('No dev server running on your machine. ' +
|
|
262
|
-
'Start Metro with `yaver dev start
|
|
262
|
+
'Start Metro with `yaver dev start`, or describe the issue in Chat so the agent can inspect it.');
|
|
263
263
|
}
|
|
264
264
|
if (lower.indexOf('no dev server') >= 0 ||
|
|
265
265
|
lower.indexOf('not running') >= 0) {
|
package/dist/P2PDogfoodDriver.js
CHANGED
|
@@ -41,8 +41,18 @@ function createP2PDogfoodDriver(client, options = {}) {
|
|
|
41
41
|
retryable: true,
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
|
+
context.log({
|
|
45
|
+
text: `[runtime] source · ${target.label}${target.platform ? ` · ${target.platform}` : ''}`,
|
|
46
|
+
at: Date.now(),
|
|
47
|
+
stream: 'system',
|
|
48
|
+
});
|
|
44
49
|
context.setPhase('starting', `Starting ${target.label} over WebRTC…`);
|
|
45
50
|
const session = await client.startDogfoodRemoteRuntime(project.workDir, project.framework, target.id);
|
|
51
|
+
context.log({
|
|
52
|
+
text: `[runtime] ${session.status || 'starting'}${session.note ? ` · ${session.note}` : ''}`,
|
|
53
|
+
at: Date.now(),
|
|
54
|
+
stream: 'system',
|
|
55
|
+
});
|
|
46
56
|
context.registerCleanup(() => client.stopDogfoodRemoteRuntime(session.id), 'session');
|
|
47
57
|
return {
|
|
48
58
|
lane: 'webrtc',
|
|
@@ -51,6 +61,13 @@ function createP2PDogfoodDriver(client, options = {}) {
|
|
|
51
61
|
};
|
|
52
62
|
}
|
|
53
63
|
context.setPhase(project.lane === 'hermes' ? 'compiling' : 'starting', project.lane === 'hermes' ? `Compiling ${project.name} with Hermes…` : `Starting ${project.name} in the browser…`);
|
|
64
|
+
context.log({
|
|
65
|
+
text: project.lane === 'hermes'
|
|
66
|
+
? `[runtime] source · Hermes build on ${project.workDir}`
|
|
67
|
+
: `[runtime] source · browser build on ${project.workDir}`,
|
|
68
|
+
at: Date.now(),
|
|
69
|
+
stream: 'system',
|
|
70
|
+
});
|
|
54
71
|
const status = await client.startDogfoodDevServer({
|
|
55
72
|
framework: project.framework,
|
|
56
73
|
workDir: project.workDir,
|
|
@@ -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);
|
|
@@ -106,6 +151,20 @@ describe('Dogfood lanes and console events', () => {
|
|
|
106
151
|
expect(swift.map((option) => [option.lane, option.supported])).toEqual([
|
|
107
152
|
['browser', false], ['hermes', false], ['webrtc', true],
|
|
108
153
|
]);
|
|
154
|
+
expect((0, DogfoodRuntime_1.dogfoodLaneOptions)('kotlin', { nativeRuntimeAvailable: true })
|
|
155
|
+
.map((option) => [option.lane, option.supported])).toEqual([
|
|
156
|
+
['browser', false], ['hermes', false], ['webrtc', true],
|
|
157
|
+
]);
|
|
158
|
+
expect((0, DogfoodRuntime_1.defaultDogfoodLane)('swift', { nativeRuntimeAvailable: true })).toBe('webrtc');
|
|
159
|
+
});
|
|
160
|
+
test('a detected browser target adds real framework exceptions such as SwiftWasm', () => {
|
|
161
|
+
const swiftWasm = (0, DogfoodRuntime_1.dogfoodLaneOptions)('swift', {
|
|
162
|
+
nativeRuntimeAvailable: false,
|
|
163
|
+
browserRuntimeAvailable: true,
|
|
164
|
+
});
|
|
165
|
+
expect(swiftWasm.map((option) => [option.lane, option.supported])).toEqual([
|
|
166
|
+
['browser', true], ['hermes', false], ['webrtc', false],
|
|
167
|
+
]);
|
|
109
168
|
});
|
|
110
169
|
test('reads raw and replayed package-manager logs from /dev/events', () => {
|
|
111
170
|
expect((0, DogfoodRuntime_1.dogfoodLogLinesFromDevEvent)({ type: 'log', logLine: '$ npm ci\u001b[0m' }))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const fs_1 = require("fs");
|
|
4
|
+
const path_1 = require("path");
|
|
5
|
+
const FeedbackModalTheme_1 = require("../FeedbackModalTheme");
|
|
6
|
+
const SOURCE_PATH = (0, path_1.join)(__dirname, '..', 'FeedbackModal.tsx');
|
|
7
|
+
describe('FeedbackModal authenticated chat contract', () => {
|
|
8
|
+
const source = (0, fs_1.readFileSync)(SOURCE_PATH, 'utf8');
|
|
9
|
+
it('themes every shared Dogfood surface for the light feedback sheet', () => {
|
|
10
|
+
expect(source).toMatch(/<DogfoodStatusRail[\s\S]*?colors=\{FEEDBACK_DOGFOOD_LIGHT_COLORS\}/);
|
|
11
|
+
expect(source).toMatch(/<DogfoodLanePicker[\s\S]*?colors=\{FEEDBACK_DOGFOOD_LIGHT_COLORS\}/);
|
|
12
|
+
expect(source).toMatch(/<DogfoodLiveConsole[\s\S]*?colors=\{FEEDBACK_DOGFOOD_CONSOLE_COLORS\}/);
|
|
13
|
+
});
|
|
14
|
+
it('keeps normal light-sheet copy at WCAG AA contrast', () => {
|
|
15
|
+
const rgb = (hex) => hex.match(/[a-f\d]{2}/gi).map((part) => parseInt(part, 16) / 255);
|
|
16
|
+
const luminance = (hex) => {
|
|
17
|
+
const channels = rgb(hex).map((channel) => channel <= 0.03928
|
|
18
|
+
? channel / 12.92
|
|
19
|
+
: ((channel + 0.055) / 1.055) ** 2.4);
|
|
20
|
+
return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2];
|
|
21
|
+
};
|
|
22
|
+
const ratio = (foreground, background) => {
|
|
23
|
+
const [lighter, darker] = [luminance(foreground), luminance(background)].sort((a, b) => b - a);
|
|
24
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
25
|
+
};
|
|
26
|
+
for (const foreground of [
|
|
27
|
+
FeedbackModalTheme_1.FEEDBACK_DOGFOOD_LIGHT_COLORS.text,
|
|
28
|
+
FeedbackModalTheme_1.FEEDBACK_DOGFOOD_LIGHT_COLORS.muted,
|
|
29
|
+
FeedbackModalTheme_1.FEEDBACK_DOGFOOD_LIGHT_COLORS.accent,
|
|
30
|
+
FeedbackModalTheme_1.FEEDBACK_DOGFOOD_LIGHT_COLORS.ready,
|
|
31
|
+
FeedbackModalTheme_1.FEEDBACK_DOGFOOD_LIGHT_COLORS.attention,
|
|
32
|
+
FeedbackModalTheme_1.FEEDBACK_DOGFOOD_LIGHT_COLORS.blocked,
|
|
33
|
+
]) {
|
|
34
|
+
expect(ratio(foreground, FeedbackModalTheme_1.FEEDBACK_DOGFOOD_LIGHT_COLORS.background)).toBeGreaterThanOrEqual(4.5);
|
|
35
|
+
}
|
|
36
|
+
expect(ratio(FeedbackModalTheme_1.FEEDBACK_DOGFOOD_CONSOLE_COLORS.text, FeedbackModalTheme_1.FEEDBACK_DOGFOOD_CONSOLE_COLORS.console))
|
|
37
|
+
.toBeGreaterThanOrEqual(4.5);
|
|
38
|
+
expect(ratio(FeedbackModalTheme_1.FEEDBACK_DOGFOOD_CONSOLE_COLORS.muted, FeedbackModalTheme_1.FEEDBACK_DOGFOOD_CONSOLE_COLORS.console))
|
|
39
|
+
.toBeGreaterThanOrEqual(4.5);
|
|
40
|
+
});
|
|
41
|
+
it('uses Chat as the authenticated entry surface without legacy command buttons', () => {
|
|
42
|
+
expect(source).toContain("useState<'chat' | 'settings'>('chat')");
|
|
43
|
+
expect(source).toContain('setShowVibeInput(authenticated || directDogfood)');
|
|
44
|
+
expect(source).not.toContain('Screenshot & Fix');
|
|
45
|
+
expect(source).not.toContain('<DeployPanel');
|
|
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
|
+
});
|
|
52
|
+
it('keeps Dogfood setup to box, runner, and checkout before asking for a runtime', () => {
|
|
53
|
+
const setupSteps = source.match(/const dogfoodSetupSteps = \[([\s\S]*?)\n \];/)?.[1] || '';
|
|
54
|
+
expect([...setupSteps.matchAll(/key: '([^']+)'/g)].map((match) => match[1]))
|
|
55
|
+
.toEqual(['box', 'runner', 'checkout']);
|
|
56
|
+
expect(setupSteps).not.toContain("key: 'oauth'");
|
|
57
|
+
expect(setupSteps).not.toContain("key: 'installation'");
|
|
58
|
+
expect(setupSteps).not.toContain("key: 'model'");
|
|
59
|
+
expect(setupSteps).not.toContain("key: 'lane'");
|
|
60
|
+
expect(source).toContain("type DogfoodSetupStage = 'setup' | 'lane' | 'runtime'");
|
|
61
|
+
expect(source).toContain('label="Choose runtime"');
|
|
62
|
+
});
|
|
63
|
+
it('passes the selected native target and labels the live log source', () => {
|
|
64
|
+
expect(source).toContain("nativeTargetId: lanePlan.preferred === 'webrtc' ? dogfoodNativeTargetId : undefined");
|
|
65
|
+
expect(source).toContain('fallbackLane: lanePlan.fallback');
|
|
66
|
+
expect(source).toContain('fallbackLane={dogfoodLanePolicy.fallback}');
|
|
67
|
+
expect(source).toMatch(/<DogfoodLiveConsole[\s\S]*?sourceLabel=\{dogfoodSourceLabel\}/);
|
|
68
|
+
expect(source).toContain('Simulator, emulator, or device');
|
|
69
|
+
});
|
|
70
|
+
it('has one iOS keyboard inset owner and no gesture-stealing sheet Pressable', () => {
|
|
71
|
+
expect(source).toContain('automaticallyAdjustKeyboardInsets={Platform.OS === \'ios\'}');
|
|
72
|
+
expect(source).not.toContain('<KeyboardAvoidingView');
|
|
73
|
+
expect(source).not.toContain('keyboardInset');
|
|
74
|
+
expect(source).toContain('<Pressable style={styles.backdrop} onPress={handleClose}');
|
|
75
|
+
expect(source).toMatch(/<View[\s\S]{0,300}?style=\{\[\s*styles\.modal/);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -4,6 +4,29 @@ const fs_1 = require("fs");
|
|
|
4
4
|
const path_1 = require("path");
|
|
5
5
|
const plugin = require('../../app.plugin.js');
|
|
6
6
|
describe('native Dogfood shortcut contract', () => {
|
|
7
|
+
it('never treats process uptime as a successful React render', () => {
|
|
8
|
+
const staleHook = `
|
|
9
|
+
// Crash-revert safety net: clear the boot-attempt counter once
|
|
10
|
+
// RN renders its first frame, OR after 10 s of uptime — whichever
|
|
11
|
+
// fires first. If neither fires (bundle crashes before render),
|
|
12
|
+
// YaverHotReload.bundleURL() will eventually revert to the
|
|
13
|
+
// TestFlight-installed bundle after 3 failed boots. See
|
|
14
|
+
// YaverHotReload.swift for the full state machine.
|
|
15
|
+
NotificationCenter.default.addObserver(
|
|
16
|
+
forName: NSNotification.Name(rawValue: "RCTContentDidAppearNotification"),
|
|
17
|
+
object: nil,
|
|
18
|
+
queue: .main
|
|
19
|
+
) { _ in YaverHotReload.markBootSuccessful() }
|
|
20
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
|
|
21
|
+
YaverHotReload.markBootSuccessful()
|
|
22
|
+
}
|
|
23
|
+
`;
|
|
24
|
+
const patched = plugin.__test.patchHotReloadBootConfirmation(staleHook);
|
|
25
|
+
expect(patched).toContain('RCTContentDidAppearNotification');
|
|
26
|
+
expect(patched).toContain('only a real first React frame confirms boot');
|
|
27
|
+
expect(patched).not.toContain('asyncAfter(deadline: .now() + 10)');
|
|
28
|
+
expect(plugin.__test.patchHotReloadBootConfirmation(patched)).toBe(patched);
|
|
29
|
+
});
|
|
7
30
|
it('patches cold and warm iOS shortcut delivery idempotently', () => {
|
|
8
31
|
const source = `
|
|
9
32
|
public class AppDelegate: ExpoAppDelegate {
|
|
@@ -43,8 +43,11 @@ describe('createP2PDogfoodDriver', () => {
|
|
|
43
43
|
})),
|
|
44
44
|
stopDogfoodRemoteRuntime: closeRuntime,
|
|
45
45
|
};
|
|
46
|
-
const
|
|
46
|
+
const snapshots = [];
|
|
47
|
+
const controller = new DogfoodRuntime_1.DogfoodController({ name: 'Native app', framework: 'flutter', workDir: '/workspace/app', lane: 'webrtc' }, (0, P2PDogfoodDriver_1.createP2PDogfoodDriver)(client), { onChange: (snapshot) => snapshots.push(snapshot.logs.map((line) => line.text)) });
|
|
47
48
|
await expect(controller.trigger()).resolves.toMatchObject({ lane: 'webrtc', sessionId: 'runtime-1' });
|
|
49
|
+
expect(snapshots.flat()).toContain('[runtime] source · iPhone simulator');
|
|
50
|
+
expect(snapshots.flat()).toContain('[runtime] starting');
|
|
48
51
|
expect(closeRuntime).not.toHaveBeenCalled();
|
|
49
52
|
await controller.stop();
|
|
50
53
|
expect(closeRuntime).toHaveBeenCalledWith('runtime-1');
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* yaver-feedback-react-native — Visual feedback SDK for Yaver.
|
|
3
3
|
*
|
|
4
|
-
* Shake-to-report surface with
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* the fix loop
|
|
4
|
+
* Shake-to-report surface with one primary action: Chat. The connected agent
|
|
5
|
+
* receives the current-screen context and can use its MCP tools for fixes,
|
|
6
|
+
* reloads, and deploys without duplicating those commands as modal buttons.
|
|
7
|
+
* Machine, runner, model, and Dogfood configuration remain under Settings.
|
|
9
8
|
*
|
|
10
9
|
* The small quick-access icon stays hidden until the first shake by
|
|
11
10
|
* default on mobile, then remains available unless the user hides it.
|
|
@@ -57,8 +56,8 @@ export { resolveSDKDogfood } from './dogfoodPolicy';
|
|
|
57
56
|
export type { DogfoodAccessSnapshot, DogfoodFlowSnapshot, SDKDogfoodConfig, SDKDogfoodStatus } from './dogfoodPolicy';
|
|
58
57
|
export { YaverDeviceDogfood } from './deviceDogfood';
|
|
59
58
|
export type { DeviceDogfoodOptions, DeviceDogfoodSession, DeviceDogfoodState } from './deviceDogfood';
|
|
60
|
-
export { DogfoodController, DogfoodRuntimeError, defaultDogfoodLane, dogfoodLaneOptions, dogfoodLogLinesFromDevEvent, runtimeLogLinesFromDevEvent, validateDogfoodProject, } from './DogfoodRuntime';
|
|
61
|
-
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';
|
|
62
61
|
export { DogfoodLanePicker, DogfoodLiveConsole, DogfoodStatusRail } from './DogfoodSessionUi';
|
|
63
62
|
export type { DogfoodStatusStep, DogfoodStatusTone, DogfoodUiColors, } from './DogfoodSessionUi';
|
|
64
63
|
export { FeedbackModal } from './FeedbackModal';
|
package/dist/index.js
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* yaver-feedback-react-native — Visual feedback SDK for Yaver.
|
|
4
4
|
*
|
|
5
|
-
* Shake-to-report surface with
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* the fix loop
|
|
5
|
+
* Shake-to-report surface with one primary action: Chat. The connected agent
|
|
6
|
+
* receives the current-screen context and can use its MCP tools for fixes,
|
|
7
|
+
* reloads, and deploys without duplicating those commands as modal buttons.
|
|
8
|
+
* Machine, runner, model, and Dogfood configuration remain under Settings.
|
|
10
9
|
*
|
|
11
10
|
* The small quick-access icon stays hidden until the first shake by
|
|
12
11
|
* default on mobile, then remains available unless the user hides it.
|
|
@@ -29,8 +28,8 @@
|
|
|
29
28
|
* ```
|
|
30
29
|
*/
|
|
31
30
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
-
exports.
|
|
33
|
-
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;
|
|
34
33
|
var YaverFeedback_1 = require("./YaverFeedback");
|
|
35
34
|
Object.defineProperty(exports, "YaverFeedback", { enumerable: true, get: function () { return YaverFeedback_1.YaverFeedback; } });
|
|
36
35
|
var storeShots_1 = require("./storeShots");
|
|
@@ -84,6 +83,7 @@ var DogfoodRuntime_1 = require("./DogfoodRuntime");
|
|
|
84
83
|
Object.defineProperty(exports, "DogfoodController", { enumerable: true, get: function () { return DogfoodRuntime_1.DogfoodController; } });
|
|
85
84
|
Object.defineProperty(exports, "DogfoodRuntimeError", { enumerable: true, get: function () { return DogfoodRuntime_1.DogfoodRuntimeError; } });
|
|
86
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; } });
|
|
87
87
|
Object.defineProperty(exports, "dogfoodLaneOptions", { enumerable: true, get: function () { return DogfoodRuntime_1.dogfoodLaneOptions; } });
|
|
88
88
|
Object.defineProperty(exports, "dogfoodLogLinesFromDevEvent", { enumerable: true, get: function () { return DogfoodRuntime_1.dogfoodLogLinesFromDevEvent; } });
|
|
89
89
|
Object.defineProperty(exports, "runtimeLogLinesFromDevEvent", { enumerable: true, get: function () { return DogfoodRuntime_1.runtimeLogLinesFromDevEvent; } });
|
package/ios/YaverHotReload.swift
CHANGED
|
@@ -157,8 +157,8 @@ class YaverHotReload: NSObject {
|
|
|
157
157
|
/// bricks the app — user has to delete + reinstall from TestFlight.
|
|
158
158
|
///
|
|
159
159
|
/// Guard: on each cold-start bundleURL() call, increment a boot
|
|
160
|
-
/// counter. Reset it on first successful render
|
|
161
|
-
///
|
|
160
|
+
/// counter. Reset it only on first successful render
|
|
161
|
+
/// (RCTContentDidAppear). If the counter hits kMaxBootAttempts
|
|
162
162
|
/// without being reset, the bundle has crashed every boot so far —
|
|
163
163
|
/// delete it and return nil so the app falls back to the
|
|
164
164
|
/// TestFlight-installed bundle.
|
|
@@ -198,8 +198,7 @@ class YaverHotReload: NSObject {
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
/// Clear the boot-attempt counter. Call this from AppDelegate after
|
|
201
|
-
/// first successful RN render (RCTContentDidAppearNotification)
|
|
202
|
-
/// after a short uptime safety timer — whichever fires first.
|
|
201
|
+
/// first successful RN render (RCTContentDidAppearNotification).
|
|
203
202
|
@objc static func markBootSuccessful() {
|
|
204
203
|
let defaults = UserDefaults.standard
|
|
205
204
|
if defaults.integer(forKey: kKeyBootAttempts) > 0 {
|
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,14 +48,28 @@ 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,
|
|
47
|
-
capabilities: {
|
|
61
|
+
capabilities: {
|
|
62
|
+
nativeRuntimeAvailable?: boolean;
|
|
63
|
+
browserRuntimeAvailable?: boolean;
|
|
64
|
+
selfDevelopment?: boolean;
|
|
65
|
+
} = {},
|
|
48
66
|
): DogfoodLaneOption[] {
|
|
49
67
|
const normalized = String(framework || '').trim().toLowerCase();
|
|
50
68
|
const reactNative = normalized === 'expo' || normalized === 'react-native';
|
|
51
|
-
|
|
69
|
+
// The framework matrix is the safe default. A positively detected browser
|
|
70
|
+
// target may add an exception (for example SwiftWasm/Tokamak), but a missing
|
|
71
|
+
// browser binary must not incorrectly remove RN/Flutter's browser build lane.
|
|
72
|
+
const browserCapable = capabilities.browserRuntimeAvailable === true || reactNative || [
|
|
52
73
|
'flutter', 'web', 'next', 'nextjs', 'vite', 'remix', 'svelte', 'vue', 'angular',
|
|
53
74
|
].includes(normalized);
|
|
54
75
|
const nativeAvailable = capabilities.nativeRuntimeAvailable === true;
|
|
@@ -68,8 +89,48 @@ export function dogfoodLaneOptions(
|
|
|
68
89
|
];
|
|
69
90
|
}
|
|
70
91
|
|
|
71
|
-
export function defaultDogfoodLane(
|
|
72
|
-
|
|
92
|
+
export function defaultDogfoodLane(
|
|
93
|
+
framework: string,
|
|
94
|
+
capabilities: {
|
|
95
|
+
nativeRuntimeAvailable?: boolean;
|
|
96
|
+
browserRuntimeAvailable?: boolean;
|
|
97
|
+
selfDevelopment?: boolean;
|
|
98
|
+
} = {},
|
|
99
|
+
): DogfoodLane {
|
|
100
|
+
const options = dogfoodLaneOptions(framework, capabilities);
|
|
101
|
+
return options.find((option) => option.default && option.supported)?.lane
|
|
102
|
+
|| options.find((option) => option.supported)?.lane
|
|
103
|
+
|| 'browser';
|
|
104
|
+
}
|
|
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
|
+
};
|
|
73
134
|
}
|
|
74
135
|
|
|
75
136
|
export interface DogfoodLogLine {
|
|
@@ -274,8 +335,8 @@ export class DogfoodController {
|
|
|
274
335
|
message: `Preparing ${this.project.name}…`, logs: [], startedAt: Date.now(),
|
|
275
336
|
});
|
|
276
337
|
|
|
277
|
-
const
|
|
278
|
-
project
|
|
338
|
+
const makeContext = (project: DogfoodProject): DogfoodRunContext => ({
|
|
339
|
+
project,
|
|
279
340
|
attempt,
|
|
280
341
|
log: (line) => {
|
|
281
342
|
if (generation !== this.generation) return;
|
|
@@ -299,16 +360,69 @@ export class DogfoodController {
|
|
|
299
360
|
this.cleanups.set(generation, owned);
|
|
300
361
|
},
|
|
301
362
|
isCurrent: () => generation === this.generation,
|
|
302
|
-
};
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
let activeProject = this.project;
|
|
366
|
+
let context = makeContext(activeProject);
|
|
303
367
|
|
|
304
368
|
try {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
+
}
|
|
312
426
|
if (!context.isCurrent()) {
|
|
313
427
|
await this.runCleanups('all', generation);
|
|
314
428
|
throw new DogfoodRuntimeError({
|
|
@@ -316,7 +430,7 @@ export class DogfoodController {
|
|
|
316
430
|
remedy: 'Wait for the newer attempt.', retryable: true,
|
|
317
431
|
});
|
|
318
432
|
}
|
|
319
|
-
this.replace({ ...this.state, phase: 'ready', message: `${
|
|
433
|
+
this.replace({ ...this.state, project: activeProject, phase: 'ready', message: `${activeProject.name} is ready`, result, failure: undefined });
|
|
320
434
|
return result;
|
|
321
435
|
} catch (error) {
|
|
322
436
|
const failure = failureFrom(error);
|