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,29 @@
|
|
|
1
|
+
import type { DogfoodUiColors } from './DogfoodSessionUi';
|
|
2
|
+
|
|
3
|
+
/** High-contrast colors for shared Dogfood controls inside the light SDK sheet. */
|
|
4
|
+
export const FEEDBACK_DOGFOOD_LIGHT_COLORS: DogfoodUiColors = {
|
|
5
|
+
background: '#ffffff',
|
|
6
|
+
border: '#c9c9d5',
|
|
7
|
+
text: '#222229',
|
|
8
|
+
muted: '#5f5f6b',
|
|
9
|
+
accent: '#5645d8',
|
|
10
|
+
accentSoft: '#e7e5ff',
|
|
11
|
+
ready: '#137a3f',
|
|
12
|
+
attention: '#9a5700',
|
|
13
|
+
blocked: '#b42318',
|
|
14
|
+
console: '#ffffff',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** The log surface stays dark, but its foregrounds are explicit and readable. */
|
|
18
|
+
export const FEEDBACK_DOGFOOD_CONSOLE_COLORS: DogfoodUiColors = {
|
|
19
|
+
...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/src/P2PClient.ts
CHANGED
|
@@ -315,7 +315,7 @@ function friendlyReloadError(status: number, body: string): string {
|
|
|
315
315
|
if (hasRefused && hasLoopback) {
|
|
316
316
|
return (
|
|
317
317
|
'No dev server running on your machine. ' +
|
|
318
|
-
'Start Metro with `yaver dev start
|
|
318
|
+
'Start Metro with `yaver dev start`, or describe the issue in Chat so the agent can inspect it.'
|
|
319
319
|
);
|
|
320
320
|
}
|
|
321
321
|
if (
|
|
@@ -491,6 +491,8 @@ export class P2PClient {
|
|
|
491
491
|
framework?: string;
|
|
492
492
|
frameworks?: string[];
|
|
493
493
|
surfaces?: string[];
|
|
494
|
+
branch?: string;
|
|
495
|
+
gitRemote?: string;
|
|
494
496
|
}>> {
|
|
495
497
|
const resp = await fetch(`${this.baseUrl}/projects`, { headers: this.authHeaders() });
|
|
496
498
|
if (!resp.ok) {
|
package/src/P2PDogfoodDriver.ts
CHANGED
|
@@ -56,8 +56,18 @@ export function createP2PDogfoodDriver(
|
|
|
56
56
|
retryable: true,
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
|
+
context.log({
|
|
60
|
+
text: `[runtime] source · ${target.label}${target.platform ? ` · ${target.platform}` : ''}`,
|
|
61
|
+
at: Date.now(),
|
|
62
|
+
stream: 'system',
|
|
63
|
+
});
|
|
59
64
|
context.setPhase('starting', `Starting ${target.label} over WebRTC…`);
|
|
60
65
|
const session = await client.startDogfoodRemoteRuntime(project.workDir, project.framework, target.id);
|
|
66
|
+
context.log({
|
|
67
|
+
text: `[runtime] ${session.status || 'starting'}${session.note ? ` · ${session.note}` : ''}`,
|
|
68
|
+
at: Date.now(),
|
|
69
|
+
stream: 'system',
|
|
70
|
+
});
|
|
61
71
|
context.registerCleanup(() => client.stopDogfoodRemoteRuntime(session.id), 'session');
|
|
62
72
|
return {
|
|
63
73
|
lane: 'webrtc',
|
|
@@ -67,6 +77,13 @@ export function createP2PDogfoodDriver(
|
|
|
67
77
|
}
|
|
68
78
|
context.setPhase(project.lane === 'hermes' ? 'compiling' : 'starting',
|
|
69
79
|
project.lane === 'hermes' ? `Compiling ${project.name} with Hermes…` : `Starting ${project.name} in the browser…`);
|
|
80
|
+
context.log({
|
|
81
|
+
text: project.lane === 'hermes'
|
|
82
|
+
? `[runtime] source · Hermes build on ${project.workDir}`
|
|
83
|
+
: `[runtime] source · browser build on ${project.workDir}`,
|
|
84
|
+
at: Date.now(),
|
|
85
|
+
stream: 'system',
|
|
86
|
+
});
|
|
70
87
|
const status = await client.startDogfoodDevServer({
|
|
71
88
|
framework: project.framework,
|
|
72
89
|
workDir: project.workDir,
|
|
@@ -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);
|
|
@@ -124,6 +175,21 @@ describe('Dogfood lanes and console events', () => {
|
|
|
124
175
|
expect(swift.map((option) => [option.lane, option.supported])).toEqual([
|
|
125
176
|
['browser', false], ['hermes', false], ['webrtc', true],
|
|
126
177
|
]);
|
|
178
|
+
expect(dogfoodLaneOptions('kotlin', { nativeRuntimeAvailable: true })
|
|
179
|
+
.map((option) => [option.lane, option.supported])).toEqual([
|
|
180
|
+
['browser', false], ['hermes', false], ['webrtc', true],
|
|
181
|
+
]);
|
|
182
|
+
expect(defaultDogfoodLane('swift', { nativeRuntimeAvailable: true })).toBe('webrtc');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('a detected browser target adds real framework exceptions such as SwiftWasm', () => {
|
|
186
|
+
const swiftWasm = dogfoodLaneOptions('swift', {
|
|
187
|
+
nativeRuntimeAvailable: false,
|
|
188
|
+
browserRuntimeAvailable: true,
|
|
189
|
+
});
|
|
190
|
+
expect(swiftWasm.map((option) => [option.lane, option.supported])).toEqual([
|
|
191
|
+
['browser', true], ['hermes', false], ['webrtc', false],
|
|
192
|
+
]);
|
|
127
193
|
});
|
|
128
194
|
|
|
129
195
|
test('reads raw and replayed package-manager logs from /dev/events', () => {
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import {
|
|
4
|
+
FEEDBACK_DOGFOOD_CONSOLE_COLORS,
|
|
5
|
+
FEEDBACK_DOGFOOD_LIGHT_COLORS,
|
|
6
|
+
} from '../FeedbackModalTheme';
|
|
7
|
+
|
|
8
|
+
const SOURCE_PATH = join(__dirname, '..', 'FeedbackModal.tsx');
|
|
9
|
+
|
|
10
|
+
describe('FeedbackModal authenticated chat contract', () => {
|
|
11
|
+
const source = readFileSync(SOURCE_PATH, 'utf8');
|
|
12
|
+
|
|
13
|
+
it('themes every shared Dogfood surface for the light feedback sheet', () => {
|
|
14
|
+
expect(source).toMatch(/<DogfoodStatusRail[\s\S]*?colors=\{FEEDBACK_DOGFOOD_LIGHT_COLORS\}/);
|
|
15
|
+
expect(source).toMatch(/<DogfoodLanePicker[\s\S]*?colors=\{FEEDBACK_DOGFOOD_LIGHT_COLORS\}/);
|
|
16
|
+
expect(source).toMatch(/<DogfoodLiveConsole[\s\S]*?colors=\{FEEDBACK_DOGFOOD_CONSOLE_COLORS\}/);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('keeps normal light-sheet copy at WCAG AA contrast', () => {
|
|
20
|
+
const rgb = (hex: string) => hex.match(/[a-f\d]{2}/gi)!.map((part) => parseInt(part, 16) / 255);
|
|
21
|
+
const luminance = (hex: string) => {
|
|
22
|
+
const channels = rgb(hex).map((channel) => channel <= 0.03928
|
|
23
|
+
? channel / 12.92
|
|
24
|
+
: ((channel + 0.055) / 1.055) ** 2.4);
|
|
25
|
+
return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2];
|
|
26
|
+
};
|
|
27
|
+
const ratio = (foreground: string, background: string) => {
|
|
28
|
+
const [lighter, darker] = [luminance(foreground), luminance(background)].sort((a, b) => b - a);
|
|
29
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
for (const foreground of [
|
|
33
|
+
FEEDBACK_DOGFOOD_LIGHT_COLORS.text,
|
|
34
|
+
FEEDBACK_DOGFOOD_LIGHT_COLORS.muted,
|
|
35
|
+
FEEDBACK_DOGFOOD_LIGHT_COLORS.accent,
|
|
36
|
+
FEEDBACK_DOGFOOD_LIGHT_COLORS.ready,
|
|
37
|
+
FEEDBACK_DOGFOOD_LIGHT_COLORS.attention,
|
|
38
|
+
FEEDBACK_DOGFOOD_LIGHT_COLORS.blocked,
|
|
39
|
+
]) {
|
|
40
|
+
expect(ratio(foreground, FEEDBACK_DOGFOOD_LIGHT_COLORS.background)).toBeGreaterThanOrEqual(4.5);
|
|
41
|
+
}
|
|
42
|
+
expect(ratio(FEEDBACK_DOGFOOD_CONSOLE_COLORS.text, FEEDBACK_DOGFOOD_CONSOLE_COLORS.console))
|
|
43
|
+
.toBeGreaterThanOrEqual(4.5);
|
|
44
|
+
expect(ratio(FEEDBACK_DOGFOOD_CONSOLE_COLORS.muted, FEEDBACK_DOGFOOD_CONSOLE_COLORS.console))
|
|
45
|
+
.toBeGreaterThanOrEqual(4.5);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('uses Chat as the authenticated entry surface without legacy command buttons', () => {
|
|
49
|
+
expect(source).toContain("useState<'chat' | 'settings'>('chat')");
|
|
50
|
+
expect(source).toContain('setShowVibeInput(authenticated || directDogfood)');
|
|
51
|
+
expect(source).not.toContain('Screenshot & Fix');
|
|
52
|
+
expect(source).not.toContain('<DeployPanel');
|
|
53
|
+
});
|
|
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
|
+
|
|
61
|
+
it('keeps Dogfood setup to box, runner, and checkout before asking for a runtime', () => {
|
|
62
|
+
const setupSteps = source.match(/const dogfoodSetupSteps = \[([\s\S]*?)\n \];/)?.[1] || '';
|
|
63
|
+
expect([...setupSteps.matchAll(/key: '([^']+)'/g)].map((match) => match[1]))
|
|
64
|
+
.toEqual(['box', 'runner', 'checkout']);
|
|
65
|
+
expect(setupSteps).not.toContain("key: 'oauth'");
|
|
66
|
+
expect(setupSteps).not.toContain("key: 'installation'");
|
|
67
|
+
expect(setupSteps).not.toContain("key: 'model'");
|
|
68
|
+
expect(setupSteps).not.toContain("key: 'lane'");
|
|
69
|
+
expect(source).toContain("type DogfoodSetupStage = 'setup' | 'lane' | 'runtime'");
|
|
70
|
+
expect(source).toContain('label="Choose runtime"');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('passes the selected native target and labels the live log source', () => {
|
|
74
|
+
expect(source).toContain("nativeTargetId: lanePlan.preferred === 'webrtc' ? dogfoodNativeTargetId : undefined");
|
|
75
|
+
expect(source).toContain('fallbackLane: lanePlan.fallback');
|
|
76
|
+
expect(source).toContain('fallbackLane={dogfoodLanePolicy.fallback}');
|
|
77
|
+
expect(source).toMatch(/<DogfoodLiveConsole[\s\S]*?sourceLabel=\{dogfoodSourceLabel\}/);
|
|
78
|
+
expect(source).toContain('Simulator, emulator, or device');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('has one iOS keyboard inset owner and no gesture-stealing sheet Pressable', () => {
|
|
82
|
+
expect(source).toContain('automaticallyAdjustKeyboardInsets={Platform.OS === \'ios\'}');
|
|
83
|
+
expect(source).not.toContain('<KeyboardAvoidingView');
|
|
84
|
+
expect(source).not.toContain('keyboardInset');
|
|
85
|
+
expect(source).toContain('<Pressable style={styles.backdrop} onPress={handleClose}');
|
|
86
|
+
expect(source).toMatch(/<View[\s\S]{0,300}?style=\{\[\s*styles\.modal/);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -3,12 +3,37 @@ import { join } from 'path';
|
|
|
3
3
|
|
|
4
4
|
const plugin = require('../../app.plugin.js') as {
|
|
5
5
|
__test: {
|
|
6
|
+
patchHotReloadBootConfirmation(contents: string): string;
|
|
6
7
|
patchDogfoodAppShortcut(contents: string): string;
|
|
7
8
|
patchDogfoodSceneDelegate(contents: string): string;
|
|
8
9
|
};
|
|
9
10
|
};
|
|
10
11
|
|
|
11
12
|
describe('native Dogfood shortcut contract', () => {
|
|
13
|
+
it('never treats process uptime as a successful React render', () => {
|
|
14
|
+
const staleHook = `
|
|
15
|
+
// Crash-revert safety net: clear the boot-attempt counter once
|
|
16
|
+
// RN renders its first frame, OR after 10 s of uptime — whichever
|
|
17
|
+
// fires first. If neither fires (bundle crashes before render),
|
|
18
|
+
// YaverHotReload.bundleURL() will eventually revert to the
|
|
19
|
+
// TestFlight-installed bundle after 3 failed boots. See
|
|
20
|
+
// YaverHotReload.swift for the full state machine.
|
|
21
|
+
NotificationCenter.default.addObserver(
|
|
22
|
+
forName: NSNotification.Name(rawValue: "RCTContentDidAppearNotification"),
|
|
23
|
+
object: nil,
|
|
24
|
+
queue: .main
|
|
25
|
+
) { _ in YaverHotReload.markBootSuccessful() }
|
|
26
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
|
|
27
|
+
YaverHotReload.markBootSuccessful()
|
|
28
|
+
}
|
|
29
|
+
`;
|
|
30
|
+
const patched = plugin.__test.patchHotReloadBootConfirmation(staleHook);
|
|
31
|
+
expect(patched).toContain('RCTContentDidAppearNotification');
|
|
32
|
+
expect(patched).toContain('only a real first React frame confirms boot');
|
|
33
|
+
expect(patched).not.toContain('asyncAfter(deadline: .now() + 10)');
|
|
34
|
+
expect(plugin.__test.patchHotReloadBootConfirmation(patched)).toBe(patched);
|
|
35
|
+
});
|
|
36
|
+
|
|
12
37
|
it('patches cold and warm iOS shortcut delivery idempotently', () => {
|
|
13
38
|
const source = `
|
|
14
39
|
public class AppDelegate: ExpoAppDelegate {
|
|
@@ -50,12 +50,16 @@ describe('createP2PDogfoodDriver', () => {
|
|
|
50
50
|
})),
|
|
51
51
|
stopDogfoodRemoteRuntime: closeRuntime,
|
|
52
52
|
} as unknown as P2PClient;
|
|
53
|
+
const snapshots: string[][] = [];
|
|
53
54
|
const controller = new DogfoodController(
|
|
54
55
|
{ name: 'Native app', framework: 'flutter', workDir: '/workspace/app', lane: 'webrtc' },
|
|
55
56
|
createP2PDogfoodDriver(client),
|
|
57
|
+
{ onChange: (snapshot) => snapshots.push(snapshot.logs.map((line) => line.text)) },
|
|
56
58
|
);
|
|
57
59
|
|
|
58
60
|
await expect(controller.trigger()).resolves.toMatchObject({ lane: 'webrtc', sessionId: 'runtime-1' });
|
|
61
|
+
expect(snapshots.flat()).toContain('[runtime] source · iPhone simulator');
|
|
62
|
+
expect(snapshots.flat()).toContain('[runtime] starting');
|
|
59
63
|
expect(closeRuntime).not.toHaveBeenCalled();
|
|
60
64
|
await controller.stop();
|
|
61
65
|
expect(closeRuntime).toHaveBeenCalledWith('runtime-1');
|
package/src/index.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.
|
|
@@ -93,6 +92,7 @@ export {
|
|
|
93
92
|
DogfoodController,
|
|
94
93
|
DogfoodRuntimeError,
|
|
95
94
|
defaultDogfoodLane,
|
|
95
|
+
dogfoodLanePlan,
|
|
96
96
|
dogfoodLaneOptions,
|
|
97
97
|
dogfoodLogLinesFromDevEvent,
|
|
98
98
|
runtimeLogLinesFromDevEvent,
|
|
@@ -104,6 +104,7 @@ export type {
|
|
|
104
104
|
DogfoodFailure,
|
|
105
105
|
DogfoodLane,
|
|
106
106
|
DogfoodLaneOption,
|
|
107
|
+
DogfoodLanePlan,
|
|
107
108
|
DogfoodLogLine,
|
|
108
109
|
DogfoodPhase,
|
|
109
110
|
DogfoodProject,
|