yaver-feedback-react-native 0.8.12 → 0.9.0
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 +13 -0
- package/dist/DeployPanel.d.ts +7 -0
- package/dist/DeployPanel.js +354 -0
- package/dist/FeedbackModal.js +408 -63
- package/dist/FloatingButton.js +25 -3
- package/dist/MachinePickerScreen.js +14 -5
- package/dist/P2PClient.d.ts +102 -1
- package/dist/P2PClient.js +313 -0
- package/dist/VibeChatScreen.d.ts +25 -0
- package/dist/VibeChatScreen.js +531 -0
- package/dist/_core/buildFeedbackPrompt.d.ts +13 -0
- package/dist/_core/buildFeedbackPrompt.js +77 -0
- package/dist/capture.d.ts +20 -0
- package/dist/capture.js +82 -0
- package/dist/preferences.d.ts +4 -0
- package/dist/preferences.js +62 -0
- package/dist/types.d.ts +66 -2
- package/dist/voice.d.ts +61 -0
- package/dist/voice.js +246 -0
- package/package.json +26 -3
- package/src/DeployPanel.tsx +403 -0
- package/src/FeedbackModal.tsx +510 -76
- package/src/FloatingButton.tsx +27 -3
- package/src/MachinePickerScreen.tsx +12 -3
- package/src/P2PClient.ts +326 -1
- package/src/VibeChatScreen.tsx +581 -0
- package/src/_core/buildFeedbackPrompt.ts +102 -0
- package/src/capture.ts +82 -0
- package/src/preferences.ts +56 -0
- package/src/types.ts +62 -2
- package/src/voice.ts +270 -0
package/src/FeedbackModal.tsx
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
Text,
|
|
13
13
|
TextInput,
|
|
14
14
|
View,
|
|
15
|
+
useWindowDimensions,
|
|
15
16
|
} from 'react-native';
|
|
16
17
|
import { YaverFeedback } from './YaverFeedback';
|
|
17
18
|
import {
|
|
@@ -24,13 +25,25 @@ import {
|
|
|
24
25
|
// stopVideoRecording,
|
|
25
26
|
} from './capture';
|
|
26
27
|
import { uploadFeedback } from './upload';
|
|
27
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
DeviceInfo,
|
|
30
|
+
FeedbackBundle,
|
|
31
|
+
OpenCodeConfigSummary,
|
|
32
|
+
OpenCodeProviderSummary,
|
|
33
|
+
RunnerAuthStatusRow,
|
|
34
|
+
} from './types';
|
|
28
35
|
import { AuthOverlay } from './AuthOverlay';
|
|
29
36
|
import { QuickActionIcon } from './QuickActionIcon';
|
|
37
|
+
import { VibeChatScreen } from './VibeChatScreen';
|
|
38
|
+
import { DeployPanel } from './DeployPanel';
|
|
30
39
|
import { listReachableDevices, RemoteDevice } from './auth';
|
|
31
40
|
import {
|
|
32
41
|
QUICK_ICON_COLOR_PRESETS,
|
|
33
42
|
QuickIconColorPreset,
|
|
43
|
+
getPreferredModel,
|
|
44
|
+
getPreferredRunner,
|
|
45
|
+
setPreferredModel,
|
|
46
|
+
setPreferredRunner,
|
|
34
47
|
} from './preferences';
|
|
35
48
|
|
|
36
49
|
/**
|
|
@@ -61,7 +74,117 @@ type MachineCardState = {
|
|
|
61
74
|
detail: string;
|
|
62
75
|
};
|
|
63
76
|
|
|
77
|
+
type RunnerTone = 'ok' | 'warning' | 'error' | 'neutral';
|
|
78
|
+
|
|
79
|
+
type RunnerCardState = {
|
|
80
|
+
id: string;
|
|
81
|
+
name: string;
|
|
82
|
+
installed: boolean;
|
|
83
|
+
authConfigured: boolean;
|
|
84
|
+
ready: boolean;
|
|
85
|
+
version?: string;
|
|
86
|
+
tone: RunnerTone;
|
|
87
|
+
statusLine: string;
|
|
88
|
+
detail?: string;
|
|
89
|
+
actionLabel?: string;
|
|
90
|
+
actionRunner?: string;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
type ProviderEditorState = {
|
|
94
|
+
mode: 'add' | 'edit';
|
|
95
|
+
id: string;
|
|
96
|
+
name: string;
|
|
97
|
+
baseUrl: string;
|
|
98
|
+
apiKey: string;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const PRIMARY_RUNNER_IDS = ['claude', 'codex', 'opencode'] as const;
|
|
102
|
+
|
|
103
|
+
function normalizeRunnerStatusRows(rows: RunnerAuthStatusRow[]): RunnerCardState[] {
|
|
104
|
+
const byId = new Map<string, RunnerAuthStatusRow>();
|
|
105
|
+
for (const row of rows) {
|
|
106
|
+
const raw = String(row.id || '').trim().toLowerCase();
|
|
107
|
+
if (!raw) continue;
|
|
108
|
+
const normalized = raw === 'claude-code' ? 'claude' : raw;
|
|
109
|
+
if (!PRIMARY_RUNNER_IDS.includes(normalized as (typeof PRIMARY_RUNNER_IDS)[number])) continue;
|
|
110
|
+
byId.set(normalized, { ...row, id: normalized });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return PRIMARY_RUNNER_IDS.map((id) => {
|
|
114
|
+
const baseName =
|
|
115
|
+
id === 'claude' ? 'Claude Code' : id === 'codex' ? 'OpenAI Codex' : 'OpenCode';
|
|
116
|
+
const row = byId.get(id);
|
|
117
|
+
if (!row) {
|
|
118
|
+
return {
|
|
119
|
+
id,
|
|
120
|
+
name: baseName,
|
|
121
|
+
installed: false,
|
|
122
|
+
authConfigured: false,
|
|
123
|
+
ready: false,
|
|
124
|
+
tone: 'warning',
|
|
125
|
+
statusLine: 'Not installed on the selected machine',
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const versionPrefix = row.version?.trim() ? `${row.version.trim()} · ` : '';
|
|
130
|
+
const detail = row.error?.trim() || row.warning?.trim() || row.detail?.trim() || undefined;
|
|
131
|
+
|
|
132
|
+
if (!row.installed) {
|
|
133
|
+
return {
|
|
134
|
+
id,
|
|
135
|
+
name: row.name || baseName,
|
|
136
|
+
installed: false,
|
|
137
|
+
authConfigured: false,
|
|
138
|
+
ready: false,
|
|
139
|
+
version: row.version,
|
|
140
|
+
tone: 'warning',
|
|
141
|
+
statusLine: 'Not installed on the selected machine',
|
|
142
|
+
detail,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (id === 'opencode') {
|
|
147
|
+
const configured = row.authConfigured || row.ready;
|
|
148
|
+
return {
|
|
149
|
+
id,
|
|
150
|
+
name: row.name || baseName,
|
|
151
|
+
installed: row.installed,
|
|
152
|
+
authConfigured: row.authConfigured,
|
|
153
|
+
ready: row.ready,
|
|
154
|
+
version: row.version,
|
|
155
|
+
tone: configured ? 'ok' : 'warning',
|
|
156
|
+
statusLine: configured
|
|
157
|
+
? `${versionPrefix}Configured on the selected machine`
|
|
158
|
+
: `${versionPrefix}Needs provider config on the selected machine`,
|
|
159
|
+
detail,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const authed = row.authConfigured || row.ready;
|
|
164
|
+
return {
|
|
165
|
+
id,
|
|
166
|
+
name: row.name || baseName,
|
|
167
|
+
installed: row.installed,
|
|
168
|
+
authConfigured: row.authConfigured,
|
|
169
|
+
ready: row.ready,
|
|
170
|
+
version: row.version,
|
|
171
|
+
tone: authed ? 'ok' : 'warning',
|
|
172
|
+
statusLine: authed
|
|
173
|
+
? `${versionPrefix}Signed in on the selected machine`
|
|
174
|
+
: `${versionPrefix}Not signed in on the selected machine`,
|
|
175
|
+
detail,
|
|
176
|
+
actionLabel: authed ? 'Re-auth' : 'Sign in',
|
|
177
|
+
actionRunner: id,
|
|
178
|
+
};
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
64
182
|
export const FeedbackModal: React.FC = () => {
|
|
183
|
+
const { width: winW, height: winH } = useWindowDimensions();
|
|
184
|
+
const isTablet = Math.min(winW, winH) >= 600;
|
|
185
|
+
// Tablet color/icon picker fans out to 5/6 cols — 31% (3-col)
|
|
186
|
+
// looks empty on a 1024pt iPad. Mobile keeps 3-col.
|
|
187
|
+
const iconOptionWidthOverride = isTablet ? '18%' : undefined;
|
|
65
188
|
const [visible, setVisible] = useState(false);
|
|
66
189
|
const [action, setAction] = useState<ActionState>('idle');
|
|
67
190
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -79,6 +202,7 @@ export const FeedbackModal: React.FC = () => {
|
|
|
79
202
|
// "pick something for me" prompt (which in 0.7.13 pointed Claude at
|
|
80
203
|
// the wrong project because the matcher grepped the prompt itself).
|
|
81
204
|
const [showVibeInput, setShowVibeInput] = useState(false);
|
|
205
|
+
const [showDeploy, setShowDeploy] = useState(false);
|
|
82
206
|
const [vibePrompt, setVibePrompt] = useState('');
|
|
83
207
|
const [lastVibeTaskId, setLastVibeTaskId] = useState<string | null>(null);
|
|
84
208
|
const [quickIconColorPreset, setQuickIconColorPreset] =
|
|
@@ -92,6 +216,14 @@ export const FeedbackModal: React.FC = () => {
|
|
|
92
216
|
title: 'No machine selected',
|
|
93
217
|
detail: 'Pick a remote dev machine before using the feedback actions.',
|
|
94
218
|
});
|
|
219
|
+
const [runnerCards, setRunnerCards] = useState<RunnerCardState[]>(() =>
|
|
220
|
+
normalizeRunnerStatusRows([]),
|
|
221
|
+
);
|
|
222
|
+
const [runnerStatusLoading, setRunnerStatusLoading] = useState(false);
|
|
223
|
+
const [runnerStatusError, setRunnerStatusError] = useState<string | null>(null);
|
|
224
|
+
const [preferredRunner, setPreferredRunnerState] = useState<string | null>(null);
|
|
225
|
+
const [preferredModel, setPreferredModelState] = useState('');
|
|
226
|
+
const [showOpenCodeConfig, setShowOpenCodeConfig] = useState(false);
|
|
95
227
|
const mountedRef = useRef(true);
|
|
96
228
|
|
|
97
229
|
const loadSelectedMachine = useCallback(async () => {
|
|
@@ -195,6 +327,69 @@ export const FeedbackModal: React.FC = () => {
|
|
|
195
327
|
}
|
|
196
328
|
}, []);
|
|
197
329
|
|
|
330
|
+
const loadRunnerStatuses = useCallback(async () => {
|
|
331
|
+
const cfg = YaverFeedback.getConfig();
|
|
332
|
+
if (!cfg?.authToken) {
|
|
333
|
+
if (mountedRef.current) {
|
|
334
|
+
setRunnerCards(normalizeRunnerStatusRows([]));
|
|
335
|
+
setRunnerStatusError('Sign in to inspect coding-agent status.');
|
|
336
|
+
setRunnerStatusLoading(false);
|
|
337
|
+
}
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (!cfg.preferredDeviceId) {
|
|
341
|
+
if (mountedRef.current) {
|
|
342
|
+
setRunnerCards(normalizeRunnerStatusRows([]));
|
|
343
|
+
setRunnerStatusError('Pick a machine to inspect coding-agent status.');
|
|
344
|
+
setRunnerStatusLoading(false);
|
|
345
|
+
}
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (mountedRef.current) {
|
|
350
|
+
setRunnerStatusLoading(true);
|
|
351
|
+
setRunnerStatusError(null);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
try {
|
|
355
|
+
let client = YaverFeedback.getP2PClient();
|
|
356
|
+
if (!client) {
|
|
357
|
+
const ok = await YaverFeedback.reconnect();
|
|
358
|
+
if (ok) client = YaverFeedback.getP2PClient();
|
|
359
|
+
}
|
|
360
|
+
if (!client) {
|
|
361
|
+
throw new Error('Not connected to the selected machine yet.');
|
|
362
|
+
}
|
|
363
|
+
const rows = await client.getRunnerAuthStatus();
|
|
364
|
+
if (mountedRef.current) {
|
|
365
|
+
setRunnerCards(normalizeRunnerStatusRows(rows));
|
|
366
|
+
}
|
|
367
|
+
} catch (err) {
|
|
368
|
+
if (mountedRef.current) {
|
|
369
|
+
setRunnerCards(normalizeRunnerStatusRows([]));
|
|
370
|
+
setRunnerStatusError(err instanceof Error ? err.message : String(err));
|
|
371
|
+
}
|
|
372
|
+
} finally {
|
|
373
|
+
if (mountedRef.current) setRunnerStatusLoading(false);
|
|
374
|
+
}
|
|
375
|
+
}, []);
|
|
376
|
+
|
|
377
|
+
const loadRoutingPrefs = useCallback(async () => {
|
|
378
|
+
try {
|
|
379
|
+
const [runner, model] = await Promise.all([
|
|
380
|
+
getPreferredRunner(),
|
|
381
|
+
getPreferredModel(),
|
|
382
|
+
]);
|
|
383
|
+
if (!mountedRef.current) return;
|
|
384
|
+
setPreferredRunnerState(runner);
|
|
385
|
+
setPreferredModelState(model ?? '');
|
|
386
|
+
} catch {
|
|
387
|
+
if (!mountedRef.current) return;
|
|
388
|
+
setPreferredRunnerState(null);
|
|
389
|
+
setPreferredModelState('');
|
|
390
|
+
}
|
|
391
|
+
}, []);
|
|
392
|
+
|
|
198
393
|
useEffect(() => {
|
|
199
394
|
mountedRef.current = true;
|
|
200
395
|
const sub = DeviceEventEmitter.addListener('yaverFeedback:startReport', () => {
|
|
@@ -220,6 +415,7 @@ export const FeedbackModal: React.FC = () => {
|
|
|
220
415
|
})
|
|
221
416
|
.catch(() => {});
|
|
222
417
|
void loadSelectedMachine();
|
|
418
|
+
void loadRunnerStatuses();
|
|
223
419
|
}
|
|
224
420
|
});
|
|
225
421
|
// Agent streams build / compile progress through the BlackBox
|
|
@@ -248,15 +444,16 @@ export const FeedbackModal: React.FC = () => {
|
|
|
248
444
|
sub.remove();
|
|
249
445
|
statusSub.remove();
|
|
250
446
|
};
|
|
251
|
-
}, [loadSelectedMachine]);
|
|
447
|
+
}, [loadRunnerStatuses, loadSelectedMachine]);
|
|
252
448
|
|
|
253
449
|
useEffect(() => {
|
|
254
450
|
if (!visible) return;
|
|
255
451
|
const interval = setInterval(() => {
|
|
256
452
|
void loadSelectedMachine();
|
|
453
|
+
void loadRunnerStatuses();
|
|
257
454
|
}, 5000);
|
|
258
455
|
return () => clearInterval(interval);
|
|
259
|
-
}, [loadSelectedMachine, visible]);
|
|
456
|
+
}, [loadRunnerStatuses, loadSelectedMachine, visible]);
|
|
260
457
|
|
|
261
458
|
useEffect(() => {
|
|
262
459
|
if (!visible) {
|
|
@@ -292,6 +489,7 @@ export const FeedbackModal: React.FC = () => {
|
|
|
292
489
|
setAction('idle');
|
|
293
490
|
setShowVibeInput(false);
|
|
294
491
|
setVibePrompt('');
|
|
492
|
+
setRunnerStatusError(null);
|
|
295
493
|
}, []);
|
|
296
494
|
|
|
297
495
|
// Helper: run a P2P call; on network failure, ask YaverFeedback to
|
|
@@ -535,6 +733,20 @@ export const FeedbackModal: React.FC = () => {
|
|
|
535
733
|
}
|
|
536
734
|
}, [showVibeInput, vibePrompt]);
|
|
537
735
|
|
|
736
|
+
// Hold the active vibe-chat session — set when handleVibingSubmit
|
|
737
|
+
// returns a fresh taskId. Renders <VibeChatScreen> which streams the
|
|
738
|
+
// SSE transcript, supports multi-turn follow-ups via /tasks/{id}/
|
|
739
|
+
// resume, and exposes a Reload button. Mirrors the in-Yaver native
|
|
740
|
+
// pane's transcript-mode behaviour, just rendered in RN here.
|
|
741
|
+
const [activeVibe, setActiveVibe] = useState<{
|
|
742
|
+
taskId: string;
|
|
743
|
+
initialPrompt: string;
|
|
744
|
+
project?: string;
|
|
745
|
+
runner?: string;
|
|
746
|
+
model?: string;
|
|
747
|
+
} | null>(null);
|
|
748
|
+
const [includeScreenshot, setIncludeScreenshot] = useState<boolean>(true);
|
|
749
|
+
|
|
538
750
|
const handleVibingSubmit = useCallback(async () => {
|
|
539
751
|
const client = YaverFeedback.getP2PClient();
|
|
540
752
|
if (!client) {
|
|
@@ -554,13 +766,53 @@ export const FeedbackModal: React.FC = () => {
|
|
|
554
766
|
.join('\n')
|
|
555
767
|
: '';
|
|
556
768
|
const userPrompt = vibePrompt.trim();
|
|
557
|
-
const
|
|
769
|
+
const promptText = userPrompt
|
|
558
770
|
? userPrompt + errNote
|
|
559
771
|
: 'Pick the next small improvement or fix for this app based on recent activity and the current screen.' +
|
|
560
772
|
errNote;
|
|
561
|
-
|
|
773
|
+
|
|
774
|
+
// Optional screenshot — captured from the host app's window.
|
|
775
|
+
// captureScreenshotBase64 returns null when react-native-view-
|
|
776
|
+
// shot isn't installed; we skip the screenshot rather than
|
|
777
|
+
// abort the whole feedback in that case.
|
|
778
|
+
let screenshotBase64: string | undefined;
|
|
779
|
+
if (includeScreenshot) {
|
|
780
|
+
const cap = await import('./capture');
|
|
781
|
+
const captured = await cap.captureScreenshotBase64();
|
|
782
|
+
if (captured?.base64) {
|
|
783
|
+
screenshotBase64 = captured.base64;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Resolve project context the same way reloadApp / vibing did.
|
|
788
|
+
const { resolveAppIdentity } = await import('./P2PClient');
|
|
789
|
+
const identity = resolveAppIdentity();
|
|
790
|
+
|
|
791
|
+
// Pull the user's preferred runner / model from local prefs.
|
|
792
|
+
// Both are optional — the agent falls back to whatever runner
|
|
793
|
+
// is signed in if neither is provided.
|
|
794
|
+
const prefs = await import('./preferences');
|
|
795
|
+
const preferredRunner = (await prefs.getPreferredRunner?.()) ?? null;
|
|
796
|
+
const preferredModel = (await prefs.getPreferredModel?.()) ?? null;
|
|
797
|
+
|
|
798
|
+
const result = await client.createFeedbackTask({
|
|
799
|
+
userPrompt: promptText,
|
|
800
|
+
projectName: identity.projectName,
|
|
801
|
+
projectPath: identity.projectPath,
|
|
802
|
+
runner: preferredRunner ?? undefined,
|
|
803
|
+
model: preferredModel ?? undefined,
|
|
804
|
+
screenshotBase64,
|
|
805
|
+
});
|
|
562
806
|
setLastVibeTaskId(result.taskId);
|
|
563
|
-
|
|
807
|
+
// Hand off to VibeChatScreen — it streams the SSE transcript,
|
|
808
|
+
// accepts follow-ups, and surfaces a Reload button.
|
|
809
|
+
setActiveVibe({
|
|
810
|
+
taskId: result.taskId,
|
|
811
|
+
initialPrompt: promptText,
|
|
812
|
+
project: identity.projectName,
|
|
813
|
+
runner: preferredRunner ?? undefined,
|
|
814
|
+
model: preferredModel ?? undefined,
|
|
815
|
+
});
|
|
564
816
|
setVibePrompt('');
|
|
565
817
|
setShowVibeInput(false);
|
|
566
818
|
} catch (err: unknown) {
|
|
@@ -568,7 +820,7 @@ export const FeedbackModal: React.FC = () => {
|
|
|
568
820
|
} finally {
|
|
569
821
|
if (mountedRef.current) setAction('idle');
|
|
570
822
|
}
|
|
571
|
-
}, [vibePrompt]);
|
|
823
|
+
}, [vibePrompt, includeScreenshot]);
|
|
572
824
|
|
|
573
825
|
/*
|
|
574
826
|
const handleScreenRecording = useCallback(async () => {
|
|
@@ -577,6 +829,48 @@ export const FeedbackModal: React.FC = () => {
|
|
|
577
829
|
*/
|
|
578
830
|
|
|
579
831
|
const busy = action !== 'idle';
|
|
832
|
+
const readyRunnerCount = runnerCards.filter((row) => row.ready || row.authConfigured).length;
|
|
833
|
+
const missingRunnerCount = runnerCards.filter((row) => !row.installed).length;
|
|
834
|
+
const needsAuthRunnerCount = runnerCards.filter(
|
|
835
|
+
(row) => row.installed && !row.authConfigured && !row.ready,
|
|
836
|
+
).length;
|
|
837
|
+
|
|
838
|
+
// Once the user fires off a vibe task, swap the entire modal body
|
|
839
|
+
// for the live chat screen. The chat manages its own SSE
|
|
840
|
+
// subscription, multi-turn follow-ups, and Reload button. Closing
|
|
841
|
+
// the chat returns to idle and clears the active vibe.
|
|
842
|
+
if (visible && activeVibe) {
|
|
843
|
+
const client = YaverFeedback.getP2PClient();
|
|
844
|
+
return (
|
|
845
|
+
<>
|
|
846
|
+
<AuthOverlay />
|
|
847
|
+
<QuickActionIcon />
|
|
848
|
+
<Modal
|
|
849
|
+
visible={visible}
|
|
850
|
+
animationType="slide"
|
|
851
|
+
transparent
|
|
852
|
+
onRequestClose={() => setActiveVibe(null)}
|
|
853
|
+
>
|
|
854
|
+
{client ? (
|
|
855
|
+
<VibeChatScreen
|
|
856
|
+
client={client}
|
|
857
|
+
initialTaskId={activeVibe.taskId}
|
|
858
|
+
initialUserPrompt={activeVibe.initialPrompt}
|
|
859
|
+
project={activeVibe.project}
|
|
860
|
+
runner={activeVibe.runner}
|
|
861
|
+
model={activeVibe.model}
|
|
862
|
+
onClose={() => setActiveVibe(null)}
|
|
863
|
+
onReload={async () => {
|
|
864
|
+
const c = YaverFeedback.getP2PClient();
|
|
865
|
+
if (!c) throw new Error('Not connected');
|
|
866
|
+
await c.reloadApp();
|
|
867
|
+
}}
|
|
868
|
+
/>
|
|
869
|
+
) : null}
|
|
870
|
+
</Modal>
|
|
871
|
+
</>
|
|
872
|
+
);
|
|
873
|
+
}
|
|
580
874
|
|
|
581
875
|
return (
|
|
582
876
|
<>
|
|
@@ -597,7 +891,21 @@ export const FeedbackModal: React.FC = () => {
|
|
|
597
891
|
pointerEvents="box-none"
|
|
598
892
|
>
|
|
599
893
|
<Pressable
|
|
600
|
-
style
|
|
894
|
+
// Tablet: cap modal width and center as a card-style
|
|
895
|
+
// sheet rather than a phone bottom sheet that stretches
|
|
896
|
+
// across a 12.9" iPad. Phone behaviour unchanged.
|
|
897
|
+
style={[
|
|
898
|
+
styles.modal,
|
|
899
|
+
isTablet
|
|
900
|
+
? {
|
|
901
|
+
width: '100%',
|
|
902
|
+
maxWidth: 640,
|
|
903
|
+
alignSelf: 'center',
|
|
904
|
+
borderTopLeftRadius: 22,
|
|
905
|
+
borderTopRightRadius: 22,
|
|
906
|
+
}
|
|
907
|
+
: null,
|
|
908
|
+
]}
|
|
601
909
|
onPress={(e) => {
|
|
602
910
|
e.stopPropagation();
|
|
603
911
|
Keyboard.dismiss();
|
|
@@ -664,6 +972,80 @@ export const FeedbackModal: React.FC = () => {
|
|
|
664
972
|
<Text style={styles.machineMeta}>{machineCard.detail}</Text>
|
|
665
973
|
</Pressable>
|
|
666
974
|
|
|
975
|
+
<View style={styles.runnerSection}>
|
|
976
|
+
<View style={styles.runnerSectionHeader}>
|
|
977
|
+
<View style={{ flex: 1 }}>
|
|
978
|
+
<Text style={styles.runnerSectionTitle}>Coding Agents</Text>
|
|
979
|
+
<Text style={styles.runnerSectionSubtitle}>
|
|
980
|
+
{runnerStatusLoading
|
|
981
|
+
? 'Refreshing runner status on the selected machine…'
|
|
982
|
+
: `${readyRunnerCount} ready · ${needsAuthRunnerCount} need sign-in · ${missingRunnerCount} missing`}
|
|
983
|
+
</Text>
|
|
984
|
+
</View>
|
|
985
|
+
<Pressable
|
|
986
|
+
onPress={() => void loadRunnerStatuses()}
|
|
987
|
+
style={({ pressed }) => [
|
|
988
|
+
styles.runnerRefreshBtn,
|
|
989
|
+
pressed && styles.buttonPressed,
|
|
990
|
+
]}
|
|
991
|
+
accessibilityRole="button"
|
|
992
|
+
accessibilityLabel="Refresh coding-agent status"
|
|
993
|
+
>
|
|
994
|
+
<Text style={styles.runnerRefreshBtnText}>
|
|
995
|
+
{runnerStatusLoading ? 'Refreshing…' : 'Refresh'}
|
|
996
|
+
</Text>
|
|
997
|
+
</Pressable>
|
|
998
|
+
</View>
|
|
999
|
+
|
|
1000
|
+
{runnerCards.map((row) => (
|
|
1001
|
+
<View
|
|
1002
|
+
key={row.id}
|
|
1003
|
+
style={[
|
|
1004
|
+
styles.runnerCard,
|
|
1005
|
+
row.tone === 'ok' && styles.runnerCardOk,
|
|
1006
|
+
row.tone === 'warning' && styles.runnerCardWarning,
|
|
1007
|
+
row.tone === 'error' && styles.runnerCardError,
|
|
1008
|
+
]}
|
|
1009
|
+
>
|
|
1010
|
+
<View style={styles.runnerCardTop}>
|
|
1011
|
+
<View style={{ flex: 1 }}>
|
|
1012
|
+
<Text style={styles.runnerCardTitle}>{row.name}</Text>
|
|
1013
|
+
<Text
|
|
1014
|
+
style={[
|
|
1015
|
+
styles.runnerCardStatus,
|
|
1016
|
+
row.tone === 'ok' && styles.runnerCardStatusOk,
|
|
1017
|
+
row.tone === 'warning' && styles.runnerCardStatusWarning,
|
|
1018
|
+
row.tone === 'error' && styles.runnerCardStatusError,
|
|
1019
|
+
]}
|
|
1020
|
+
>
|
|
1021
|
+
{row.statusLine}
|
|
1022
|
+
</Text>
|
|
1023
|
+
</View>
|
|
1024
|
+
{row.actionRunner ? (
|
|
1025
|
+
<Pressable
|
|
1026
|
+
onPress={() => setRunnerAuthModal(row.actionRunner ?? null)}
|
|
1027
|
+
style={({ pressed }) => [
|
|
1028
|
+
styles.runnerActionBtn,
|
|
1029
|
+
pressed && styles.buttonPressed,
|
|
1030
|
+
]}
|
|
1031
|
+
accessibilityRole="button"
|
|
1032
|
+
accessibilityLabel={`${row.actionLabel} ${row.name}`}
|
|
1033
|
+
>
|
|
1034
|
+
<Text style={styles.runnerActionBtnText}>{row.actionLabel}</Text>
|
|
1035
|
+
</Pressable>
|
|
1036
|
+
) : null}
|
|
1037
|
+
</View>
|
|
1038
|
+
{row.detail ? (
|
|
1039
|
+
<Text style={styles.runnerCardDetail}>{row.detail}</Text>
|
|
1040
|
+
) : null}
|
|
1041
|
+
</View>
|
|
1042
|
+
))}
|
|
1043
|
+
|
|
1044
|
+
{runnerStatusError ? (
|
|
1045
|
+
<Text style={styles.runnerSectionError}>{runnerStatusError}</Text>
|
|
1046
|
+
) : null}
|
|
1047
|
+
</View>
|
|
1048
|
+
|
|
667
1049
|
{quickIconHidden && (
|
|
668
1050
|
<View style={styles.quickIconNote}>
|
|
669
1051
|
<Text style={styles.quickIconNoteText}>
|
|
@@ -703,6 +1085,7 @@ export const FeedbackModal: React.FC = () => {
|
|
|
703
1085
|
}}
|
|
704
1086
|
style={[
|
|
705
1087
|
styles.iconOption,
|
|
1088
|
+
iconOptionWidthOverride ? { width: iconOptionWidthOverride } : null,
|
|
706
1089
|
selected && styles.iconOptionSelected,
|
|
707
1090
|
]}
|
|
708
1091
|
>
|
|
@@ -811,40 +1194,22 @@ export const FeedbackModal: React.FC = () => {
|
|
|
811
1194
|
busy={action === 'capturing'}
|
|
812
1195
|
/>
|
|
813
1196
|
|
|
814
|
-
{/*
|
|
815
|
-
on the
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
busy && runnerAuthRowStyles.buttonDisabled,
|
|
826
|
-
]}
|
|
827
|
-
accessibilityRole="button"
|
|
828
|
-
accessibilityLabel="Remote sign-in Codex"
|
|
829
|
-
>
|
|
830
|
-
<Text style={runnerAuthRowStyles.buttonLabel}>Remote sign-in</Text>
|
|
831
|
-
<Text style={runnerAuthRowStyles.buttonName}>Codex</Text>
|
|
832
|
-
</Pressable>
|
|
833
|
-
<Pressable
|
|
834
|
-
onPress={() => setRunnerAuthModal('claude')}
|
|
1197
|
+
{/* Deploy — opens an inline panel that talks to
|
|
1198
|
+
/fleet/deploy-options on the agent and lets the user
|
|
1199
|
+
pick TestFlight / Play / Both, then a machine to run
|
|
1200
|
+
it on. Capabilities (e.g. "Linux can't TestFlight")
|
|
1201
|
+
come from the agent's doctor probes — no client-side
|
|
1202
|
+
platform smarts here. */}
|
|
1203
|
+
{!showDeploy ? (
|
|
1204
|
+
<ActionRow
|
|
1205
|
+
label="Deploy"
|
|
1206
|
+
tint="#7f8cf7"
|
|
1207
|
+
onPress={() => setShowDeploy(true)}
|
|
835
1208
|
disabled={busy}
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
]}
|
|
841
|
-
accessibilityRole="button"
|
|
842
|
-
accessibilityLabel="Remote sign-in Claude"
|
|
843
|
-
>
|
|
844
|
-
<Text style={runnerAuthRowStyles.buttonLabel}>Remote sign-in</Text>
|
|
845
|
-
<Text style={runnerAuthRowStyles.buttonName}>Claude</Text>
|
|
846
|
-
</Pressable>
|
|
847
|
-
</View>
|
|
1209
|
+
/>
|
|
1210
|
+
) : (
|
|
1211
|
+
<DeployPanel onClose={() => setShowDeploy(false)} />
|
|
1212
|
+
)}
|
|
848
1213
|
|
|
849
1214
|
{progress !== null && (
|
|
850
1215
|
<View style={styles.progressTrack}>
|
|
@@ -879,7 +1244,10 @@ export const FeedbackModal: React.FC = () => {
|
|
|
879
1244
|
{runnerAuthModal ? (
|
|
880
1245
|
<RunnerAuthNativeModal
|
|
881
1246
|
runner={runnerAuthModal}
|
|
882
|
-
onClose={() =>
|
|
1247
|
+
onClose={() => {
|
|
1248
|
+
setRunnerAuthModal(null);
|
|
1249
|
+
void loadRunnerStatuses();
|
|
1250
|
+
}}
|
|
883
1251
|
/>
|
|
884
1252
|
) : null}
|
|
885
1253
|
</>
|
|
@@ -1107,6 +1475,106 @@ const styles = StyleSheet.create({
|
|
|
1107
1475
|
marginTop: 4,
|
|
1108
1476
|
lineHeight: 17,
|
|
1109
1477
|
},
|
|
1478
|
+
runnerSection: {
|
|
1479
|
+
marginTop: 2,
|
|
1480
|
+
gap: 10,
|
|
1481
|
+
},
|
|
1482
|
+
runnerSectionHeader: {
|
|
1483
|
+
flexDirection: 'row',
|
|
1484
|
+
alignItems: 'center',
|
|
1485
|
+
gap: 10,
|
|
1486
|
+
},
|
|
1487
|
+
runnerSectionTitle: {
|
|
1488
|
+
color: '#f8fafc',
|
|
1489
|
+
fontSize: 16,
|
|
1490
|
+
fontWeight: '700',
|
|
1491
|
+
},
|
|
1492
|
+
runnerSectionSubtitle: {
|
|
1493
|
+
marginTop: 2,
|
|
1494
|
+
color: '#94a3b8',
|
|
1495
|
+
fontSize: 12,
|
|
1496
|
+
},
|
|
1497
|
+
runnerRefreshBtn: {
|
|
1498
|
+
borderRadius: 10,
|
|
1499
|
+
borderWidth: 1,
|
|
1500
|
+
borderColor: 'rgba(148,163,184,0.22)',
|
|
1501
|
+
backgroundColor: 'rgba(15,23,42,0.65)',
|
|
1502
|
+
paddingHorizontal: 10,
|
|
1503
|
+
paddingVertical: 8,
|
|
1504
|
+
},
|
|
1505
|
+
runnerRefreshBtnText: {
|
|
1506
|
+
color: '#cbd5e1',
|
|
1507
|
+
fontSize: 12,
|
|
1508
|
+
fontWeight: '600',
|
|
1509
|
+
},
|
|
1510
|
+
runnerCard: {
|
|
1511
|
+
borderRadius: 12,
|
|
1512
|
+
borderWidth: 1,
|
|
1513
|
+
borderColor: 'rgba(148,163,184,0.14)',
|
|
1514
|
+
backgroundColor: 'rgba(15,23,42,0.45)',
|
|
1515
|
+
paddingHorizontal: 12,
|
|
1516
|
+
paddingVertical: 11,
|
|
1517
|
+
gap: 6,
|
|
1518
|
+
},
|
|
1519
|
+
runnerCardOk: {
|
|
1520
|
+
borderColor: 'rgba(34,197,94,0.28)',
|
|
1521
|
+
backgroundColor: 'rgba(20,83,45,0.20)',
|
|
1522
|
+
},
|
|
1523
|
+
runnerCardWarning: {
|
|
1524
|
+
borderColor: 'rgba(251,191,36,0.28)',
|
|
1525
|
+
backgroundColor: 'rgba(120,53,15,0.18)',
|
|
1526
|
+
},
|
|
1527
|
+
runnerCardError: {
|
|
1528
|
+
borderColor: 'rgba(248,113,113,0.28)',
|
|
1529
|
+
backgroundColor: 'rgba(127,29,29,0.18)',
|
|
1530
|
+
},
|
|
1531
|
+
runnerCardTop: {
|
|
1532
|
+
flexDirection: 'row',
|
|
1533
|
+
alignItems: 'center',
|
|
1534
|
+
gap: 10,
|
|
1535
|
+
},
|
|
1536
|
+
runnerCardTitle: {
|
|
1537
|
+
color: '#f8fafc',
|
|
1538
|
+
fontSize: 14,
|
|
1539
|
+
fontWeight: '700',
|
|
1540
|
+
},
|
|
1541
|
+
runnerCardStatus: {
|
|
1542
|
+
marginTop: 2,
|
|
1543
|
+
fontSize: 12,
|
|
1544
|
+
color: '#cbd5e1',
|
|
1545
|
+
},
|
|
1546
|
+
runnerCardStatusOk: {
|
|
1547
|
+
color: '#86efac',
|
|
1548
|
+
},
|
|
1549
|
+
runnerCardStatusWarning: {
|
|
1550
|
+
color: '#fcd34d',
|
|
1551
|
+
},
|
|
1552
|
+
runnerCardStatusError: {
|
|
1553
|
+
color: '#fca5a5',
|
|
1554
|
+
},
|
|
1555
|
+
runnerCardDetail: {
|
|
1556
|
+
color: '#94a3b8',
|
|
1557
|
+
fontSize: 11,
|
|
1558
|
+
lineHeight: 16,
|
|
1559
|
+
},
|
|
1560
|
+
runnerActionBtn: {
|
|
1561
|
+
borderRadius: 10,
|
|
1562
|
+
borderWidth: 1,
|
|
1563
|
+
borderColor: 'rgba(129,140,248,0.35)',
|
|
1564
|
+
backgroundColor: 'rgba(67,56,202,0.22)',
|
|
1565
|
+
paddingHorizontal: 12,
|
|
1566
|
+
paddingVertical: 8,
|
|
1567
|
+
},
|
|
1568
|
+
runnerActionBtnText: {
|
|
1569
|
+
color: '#c7d2fe',
|
|
1570
|
+
fontSize: 12,
|
|
1571
|
+
fontWeight: '700',
|
|
1572
|
+
},
|
|
1573
|
+
runnerSectionError: {
|
|
1574
|
+
color: '#fca5a5',
|
|
1575
|
+
fontSize: 12,
|
|
1576
|
+
lineHeight: 18,
|
|
1577
|
+
},
|
|
1110
1578
|
captureChoices: {
|
|
1111
1579
|
gap: 10,
|
|
1112
1580
|
},
|
|
@@ -1484,40 +1952,6 @@ const RunnerAuthNativeModal: React.FC<{
|
|
|
1484
1952
|
);
|
|
1485
1953
|
};
|
|
1486
1954
|
|
|
1487
|
-
const runnerAuthRowStyles = StyleSheet.create({
|
|
1488
|
-
container: {
|
|
1489
|
-
flexDirection: 'row',
|
|
1490
|
-
gap: 8,
|
|
1491
|
-
marginTop: 8,
|
|
1492
|
-
flexWrap: 'wrap',
|
|
1493
|
-
},
|
|
1494
|
-
button: {
|
|
1495
|
-
flexGrow: 1,
|
|
1496
|
-
flexBasis: 0,
|
|
1497
|
-
minWidth: 120,
|
|
1498
|
-
paddingHorizontal: 12,
|
|
1499
|
-
paddingVertical: 10,
|
|
1500
|
-
borderRadius: 10,
|
|
1501
|
-
borderWidth: 1,
|
|
1502
|
-
borderColor: 'rgba(148,163,184,0.22)',
|
|
1503
|
-
backgroundColor: 'rgba(15,23,42,0.6)',
|
|
1504
|
-
},
|
|
1505
|
-
buttonPressed: { opacity: 0.7 },
|
|
1506
|
-
buttonDisabled: { opacity: 0.4 },
|
|
1507
|
-
buttonLabel: {
|
|
1508
|
-
fontSize: 10,
|
|
1509
|
-
color: '#94a3b8',
|
|
1510
|
-
textTransform: 'uppercase',
|
|
1511
|
-
letterSpacing: 0.8,
|
|
1512
|
-
},
|
|
1513
|
-
buttonName: {
|
|
1514
|
-
marginTop: 2,
|
|
1515
|
-
fontSize: 14,
|
|
1516
|
-
fontWeight: '600',
|
|
1517
|
-
color: '#f1f5f9',
|
|
1518
|
-
},
|
|
1519
|
-
});
|
|
1520
|
-
|
|
1521
1955
|
const runnerAuthModalStyles = StyleSheet.create({
|
|
1522
1956
|
overlay: {
|
|
1523
1957
|
flex: 1,
|