yaver-feedback-react-native 0.5.2 → 0.5.4

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.
Files changed (50) hide show
  1. package/README.md +8 -0
  2. package/dist/AuthOverlay.d.ts +16 -0
  3. package/dist/AuthOverlay.js +104 -0
  4. package/dist/BlackBox.d.ts +154 -0
  5. package/dist/BlackBox.js +395 -0
  6. package/dist/ConnectionScreen.d.ts +13 -0
  7. package/dist/ConnectionScreen.js +373 -0
  8. package/dist/Discovery.d.ts +59 -0
  9. package/dist/Discovery.js +293 -0
  10. package/dist/FeedbackModal.d.ts +11 -0
  11. package/dist/FeedbackModal.js +623 -0
  12. package/dist/FixReport.d.ts +23 -0
  13. package/dist/FixReport.js +282 -0
  14. package/dist/FloatingButton.d.ts +71 -0
  15. package/dist/FloatingButton.js +778 -0
  16. package/dist/LoginScreen.d.ts +14 -0
  17. package/dist/LoginScreen.js +317 -0
  18. package/dist/MachinePickerScreen.d.ts +19 -0
  19. package/dist/MachinePickerScreen.js +175 -0
  20. package/dist/P2PClient.d.ts +136 -0
  21. package/dist/P2PClient.js +357 -0
  22. package/dist/ShakeDetector.d.ts +39 -0
  23. package/dist/ShakeDetector.js +111 -0
  24. package/dist/YaverFeedback.d.ts +198 -0
  25. package/dist/YaverFeedback.js +679 -0
  26. package/dist/YaverUpdates.d.ts +78 -0
  27. package/dist/YaverUpdates.js +272 -0
  28. package/dist/__tests__/Discovery.test.d.ts +1 -0
  29. package/dist/__tests__/Discovery.test.js +164 -0
  30. package/dist/__tests__/P2PClient.test.d.ts +1 -0
  31. package/dist/__tests__/P2PClient.test.js +169 -0
  32. package/dist/__tests__/SDKToken.test.d.ts +1 -0
  33. package/dist/__tests__/SDKToken.test.js +215 -0
  34. package/dist/__tests__/YaverFeedback.test.d.ts +1 -0
  35. package/dist/__tests__/YaverFeedback.test.js +161 -0
  36. package/dist/__tests__/types.test.d.ts +1 -0
  37. package/dist/__tests__/types.test.js +219 -0
  38. package/dist/auth.d.ts +105 -0
  39. package/dist/auth.js +282 -0
  40. package/dist/capture.d.ts +27 -0
  41. package/dist/capture.js +74 -0
  42. package/dist/expo.d.ts +15 -0
  43. package/dist/expo.js +62 -0
  44. package/dist/index.d.ts +48 -0
  45. package/dist/index.js +80 -0
  46. package/dist/types.d.ts +282 -0
  47. package/dist/types.js +2 -0
  48. package/dist/upload.d.ts +13 -0
  49. package/dist/upload.js +59 -0
  50. package/package.json +10 -6
@@ -0,0 +1,778 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.FloatingButton = void 0;
37
+ const react_1 = __importStar(require("react"));
38
+ const react_native_1 = require("react-native");
39
+ const YaverFeedback_1 = require("./YaverFeedback");
40
+ const FixReport_1 = require("./FixReport");
41
+ const BlackBox_1 = require("./BlackBox");
42
+ const DEFAULT_SIZE = 40;
43
+ const DEFAULT_COLOR = '#6366f1';
44
+ const DEFAULT_PANEL_BG = '#2d2d2d';
45
+ /**
46
+ * Draggable debug console button for the Yaver Feedback SDK.
47
+ *
48
+ * Drop this into any React Native app for an instant debug console
49
+ * with message back-and-forth, hot reload, and build+deploy:
50
+ *
51
+ * ```tsx
52
+ * import { FloatingButton } from '@yaver/feedback-react-native';
53
+ *
54
+ * function App() {
55
+ * return (
56
+ * <>
57
+ * <YourApp />
58
+ * <FloatingButton />
59
+ * </>
60
+ * );
61
+ * }
62
+ * ```
63
+ *
64
+ * Features:
65
+ * - **Tap** → expand terminal-style console panel
66
+ * - **Drag** → reposition anywhere
67
+ * - **Type** → send tasks to the AI agent, see responses
68
+ * - **Hot Reload** → trigger hot reload
69
+ * - **Build iOS** → build + auto-submit to TestFlight
70
+ * - **Build Android** → build + auto-submit to Play Store
71
+ * - **"quit"** → disable the SDK
72
+ */
73
+ const FloatingButton = ({ onPress, initialPosition, size = DEFAULT_SIZE, color = DEFAULT_COLOR, showStatusDot = true, style: stylePreset = 'terminal', icon, agentUrl: agentUrlProp, authToken: authTokenProp, healthCheckInterval = 5000, panelBackgroundColor, }) => {
74
+ const { width: screenWidth } = react_native_1.Dimensions.get('window');
75
+ const defaultX = initialPosition?.x ?? 10;
76
+ const defaultY = initialPosition?.y ?? 90;
77
+ const pan = (0, react_1.useRef)(new react_native_1.Animated.ValueXY({ x: defaultX, y: defaultY })).current;
78
+ const isDragging = (0, react_1.useRef)(false);
79
+ const [chatOpen, setChatOpen] = (0, react_1.useState)(false);
80
+ const [fullSize, setFullSize] = (0, react_1.useState)(false);
81
+ const [message, setMessage] = (0, react_1.useState)('');
82
+ const [sending, setSending] = (0, react_1.useState)(false);
83
+ const [output, setOutput] = (0, react_1.useState)([]);
84
+ const [reloading, setReloading] = (0, react_1.useState)(false);
85
+ const [isConnected, setIsConnected] = (0, react_1.useState)(false);
86
+ const [testSession, setTestSession] = (0, react_1.useState)(null);
87
+ const [showFixReport, setShowFixReport] = (0, react_1.useState)(false);
88
+ const testPollRef = (0, react_1.useRef)(null);
89
+ const outputScrollRef = (0, react_1.useRef)(null);
90
+ // Resolve agent URL and token — re-read from config each render
91
+ // because discoverAgent() may set agentUrl asynchronously after init.
92
+ const [resolvedAgentUrl, setResolvedAgentUrl] = (0, react_1.useState)(agentUrlProp || YaverFeedback_1.YaverFeedback.getConfig()?.agentUrl);
93
+ const config = YaverFeedback_1.YaverFeedback.getConfig();
94
+ const agentUrl = resolvedAgentUrl;
95
+ const authToken = authTokenProp || config?.authToken;
96
+ const panelBg = panelBackgroundColor || config?.panelBackgroundColor || DEFAULT_PANEL_BG;
97
+ const addOutput = (0, react_1.useCallback)((line) => {
98
+ setOutput((prev) => [...prev.slice(-20), line]);
99
+ }, []);
100
+ // Connection health polling — also picks up agentUrl from config when
101
+ // it becomes available after background discovery completes.
102
+ (0, react_1.useEffect)(() => {
103
+ if (!healthCheckInterval)
104
+ return;
105
+ const check = async () => {
106
+ // Re-read config in case discoverAgent() resolved since last check
107
+ const latestUrl = agentUrlProp || YaverFeedback_1.YaverFeedback.getConfig()?.agentUrl;
108
+ if (latestUrl && latestUrl !== resolvedAgentUrl) {
109
+ setResolvedAgentUrl(latestUrl);
110
+ }
111
+ if (!latestUrl)
112
+ return;
113
+ try {
114
+ const client = YaverFeedback_1.YaverFeedback.getP2PClient();
115
+ if (client) {
116
+ setIsConnected(await client.health());
117
+ }
118
+ else {
119
+ const controller = new AbortController();
120
+ const timeout = setTimeout(() => controller.abort(), 3000);
121
+ const resp = await fetch(`${latestUrl.replace(/\/$/, '')}/health`, {
122
+ signal: controller.signal,
123
+ });
124
+ clearTimeout(timeout);
125
+ setIsConnected(resp.ok);
126
+ }
127
+ }
128
+ catch {
129
+ setIsConnected(false);
130
+ }
131
+ };
132
+ check();
133
+ const interval = setInterval(check, healthCheckInterval);
134
+ return () => clearInterval(interval);
135
+ }, [agentUrlProp, healthCheckInterval, resolvedAgentUrl]);
136
+ const panResponder = (0, react_1.useRef)(react_native_1.PanResponder.create({
137
+ onStartShouldSetPanResponder: () => true,
138
+ onMoveShouldSetPanResponder: (_, gs) => Math.abs(gs.dx) > 4 || Math.abs(gs.dy) > 4,
139
+ onPanResponderGrant: () => {
140
+ pan.extractOffset();
141
+ isDragging.current = false;
142
+ },
143
+ onPanResponderMove: (_, gs) => {
144
+ if (Math.abs(gs.dx) > 4 || Math.abs(gs.dy) > 4)
145
+ isDragging.current = true;
146
+ react_native_1.Animated.event([null, { dx: pan.x, dy: pan.y }], { useNativeDriver: false })(_, gs);
147
+ },
148
+ onPanResponderRelease: () => pan.flattenOffset(),
149
+ })).current;
150
+ const handleTap = (0, react_1.useCallback)(() => {
151
+ if (isDragging.current)
152
+ return;
153
+ if (onPress) {
154
+ onPress();
155
+ }
156
+ else {
157
+ setChatOpen((prev) => !prev);
158
+ }
159
+ }, [onPress]);
160
+ // Send message → create task → poll for response
161
+ const handleSend = (0, react_1.useCallback)(async () => {
162
+ if (!message.trim() || !agentUrl || !authToken)
163
+ return;
164
+ const msg = message.trim();
165
+ setSending(true);
166
+ setMessage('');
167
+ react_native_1.Keyboard.dismiss();
168
+ addOutput(`> ${msg}`);
169
+ try {
170
+ const url = agentUrl.replace(/\/$/, '');
171
+ const resp = await fetch(`${url}/tasks`, {
172
+ method: 'POST',
173
+ headers: { Authorization: `Bearer ${authToken}`, 'Content-Type': 'application/json' },
174
+ body: JSON.stringify({ title: msg, source: 'feedback-console' }),
175
+ });
176
+ if (!resp.ok) {
177
+ addOutput(`err: ${resp.status}`);
178
+ setSending(false);
179
+ return;
180
+ }
181
+ const data = await resp.json();
182
+ const taskId = data.taskId ?? data.id ?? data.task?.id;
183
+ if (!taskId) {
184
+ addOutput('task created (no id)');
185
+ setSending(false);
186
+ return;
187
+ }
188
+ addOutput(`task ${taskId} started...`);
189
+ BlackBox_1.BlackBox.log(`Console task: ${msg}`, 'FloatingButton');
190
+ // Poll task output for up to 60s
191
+ let attempts = 0;
192
+ const poll = setInterval(async () => {
193
+ attempts++;
194
+ try {
195
+ const sr = await fetch(`${url}/tasks/${taskId}`, {
196
+ headers: { Authorization: `Bearer ${authToken}` },
197
+ });
198
+ if (!sr.ok) {
199
+ clearInterval(poll);
200
+ setSending(false);
201
+ return;
202
+ }
203
+ const task = await sr.json();
204
+ const t = task.task ?? task;
205
+ if (t.status === 'completed' || t.status === 'failed' || t.status === 'stopped') {
206
+ const out = t.output ?? t.rawOutput ?? '';
207
+ if (out) {
208
+ const lines = out.split('\n').filter((l) => l.trim());
209
+ for (const l of lines.slice(-5))
210
+ addOutput(l.slice(0, 80));
211
+ }
212
+ addOutput(t.status === 'completed' ? 'done.' : `${t.status}.`);
213
+ clearInterval(poll);
214
+ setSending(false);
215
+ }
216
+ else if (attempts >= 30) {
217
+ addOutput('running in background...');
218
+ clearInterval(poll);
219
+ setSending(false);
220
+ }
221
+ }
222
+ catch {
223
+ clearInterval(poll);
224
+ setSending(false);
225
+ }
226
+ }, 2000);
227
+ }
228
+ catch (e) {
229
+ addOutput(`fail: ${String(e).slice(0, 50)}`);
230
+ setSending(false);
231
+ }
232
+ }, [message, agentUrl, authToken, addOutput]);
233
+ // Generic action: send task to agent, poll for output
234
+ const runAgentAction = (0, react_1.useCallback)(async (label, prompt) => {
235
+ if (!agentUrl || !authToken)
236
+ return;
237
+ addOutput(`> ${label}`);
238
+ setSending(true);
239
+ try {
240
+ const url = agentUrl.replace(/\/$/, '');
241
+ const resp = await fetch(`${url}/tasks`, {
242
+ method: 'POST',
243
+ headers: { Authorization: `Bearer ${authToken}`, 'Content-Type': 'application/json' },
244
+ body: JSON.stringify({
245
+ title: prompt,
246
+ source: 'feedback-sdk',
247
+ description: `[Feedback SDK] User triggered "${label}" from the debug console.`,
248
+ }),
249
+ });
250
+ if (!resp.ok) {
251
+ addOutput(`err: ${resp.status}`);
252
+ setSending(false);
253
+ return;
254
+ }
255
+ const data = await resp.json();
256
+ const taskId = data.taskId ?? data.id ?? data.task?.id;
257
+ if (!taskId) {
258
+ addOutput('started (no id)');
259
+ setSending(false);
260
+ return;
261
+ }
262
+ addOutput(`${label}: task ${taskId}...`);
263
+ BlackBox_1.BlackBox.log(`Action: ${label}`, 'FloatingButton');
264
+ // Poll output
265
+ let attempts = 0;
266
+ const poll = setInterval(async () => {
267
+ attempts++;
268
+ try {
269
+ const sr = await fetch(`${url}/tasks/${taskId}`, {
270
+ headers: { Authorization: `Bearer ${authToken}` },
271
+ });
272
+ if (!sr.ok) {
273
+ clearInterval(poll);
274
+ setSending(false);
275
+ return;
276
+ }
277
+ const task = await sr.json();
278
+ const t = task.task ?? task;
279
+ if (t.status === 'completed' || t.status === 'failed' || t.status === 'stopped') {
280
+ const out = t.output ?? t.rawOutput ?? '';
281
+ if (out) {
282
+ for (const l of out.split('\n').filter((l) => l.trim()).slice(-5)) {
283
+ addOutput(l.slice(0, 80));
284
+ }
285
+ }
286
+ addOutput(t.status === 'completed' ? 'done.' : `${t.status}.`);
287
+ clearInterval(poll);
288
+ setSending(false);
289
+ }
290
+ else if (attempts >= 60) {
291
+ addOutput('running in background...');
292
+ clearInterval(poll);
293
+ setSending(false);
294
+ }
295
+ }
296
+ catch {
297
+ clearInterval(poll);
298
+ setSending(false);
299
+ }
300
+ }, 2000);
301
+ }
302
+ catch (e) {
303
+ addOutput(`fail: ${String(e).slice(0, 50)}`);
304
+ setSending(false);
305
+ }
306
+ }, [agentUrl, authToken, addOutput]);
307
+ const handleReload = (0, react_1.useCallback)(() => {
308
+ // @ts-ignore — __DEV__ is defined by React Native bundler
309
+ const isDev = typeof __DEV__ !== 'undefined' ? __DEV__ : true;
310
+ if (isDev) {
311
+ runAgentAction('hot-reload', 'Hot reload the app. Send the reload signal to the dev server to trigger a fast refresh.');
312
+ }
313
+ else {
314
+ runAgentAction('rebuild', 'This is a release build — hot reload is not available. ' +
315
+ 'Rebuild the app using native tools (xcodebuild for iOS, gradle for Android — no Expo) ' +
316
+ 'and upload to TestFlight/Play Store. Auto-increment build number. Report progress.');
317
+ }
318
+ }, [runAgentAction]);
319
+ // Build config from SDK settings
320
+ const buildPlatforms = config?.buildPlatforms ?? 'both';
321
+ const autoDeploy = config?.autoDeploy !== false; // default true
322
+ const handleBuild = (0, react_1.useCallback)(() => {
323
+ const platforms = buildPlatforms === 'both' ? ['ios', 'android'] : [buildPlatforms];
324
+ const parts = [];
325
+ for (const p of platforms) {
326
+ if (p === 'ios') {
327
+ parts.push(autoDeploy
328
+ ? 'Build the iOS app, archive, and upload to TestFlight. Auto-increment the build number.'
329
+ : 'Build the iOS app and archive it locally. Auto-increment the build number. Do NOT upload to TestFlight.');
330
+ }
331
+ else if (p === 'android') {
332
+ parts.push(autoDeploy
333
+ ? 'Build the Android app (release AAB) and upload to Google Play internal testing. Auto-increment the versionCode.'
334
+ : 'Build the Android app (release AAB) locally. Auto-increment the versionCode. Do NOT upload to Play Store.');
335
+ }
336
+ else if (p === 'web') {
337
+ parts.push('Build the web app for production.');
338
+ }
339
+ }
340
+ const deployLabel = autoDeploy ? ' + deploy' : '';
341
+ const platformLabel = buildPlatforms === 'both' ? 'iOS & Android' : buildPlatforms;
342
+ runAgentAction(`build-${platformLabel}${deployLabel}`, parts.join(' Then, ') + ' Report progress and result.');
343
+ }, [runAgentAction, buildPlatforms, autoDeploy]);
344
+ const handleBugReport = (0, react_1.useCallback)(async () => {
345
+ if (!agentUrl || !authToken)
346
+ return;
347
+ addOutput('> bug report');
348
+ setSending(true);
349
+ try {
350
+ // Try to capture screenshot (without SDK overlay)
351
+ let screenshotUri;
352
+ try {
353
+ const { captureScreenshot } = require('./capture');
354
+ screenshotUri = await captureScreenshot();
355
+ }
356
+ catch {
357
+ // Screenshot capture not available — send text-only report
358
+ }
359
+ const url = agentUrl.replace(/\/$/, '');
360
+ if (screenshotUri) {
361
+ // Upload screenshot as feedback with bug flag
362
+ const formData = new FormData();
363
+ formData.append('metadata', JSON.stringify({
364
+ timestamp: new Date().toISOString(),
365
+ type: 'bug-report',
366
+ source: 'feedback-sdk',
367
+ }));
368
+ formData.append('screenshot_0', {
369
+ uri: screenshotUri,
370
+ type: 'image/png',
371
+ name: 'bug_screenshot.png',
372
+ });
373
+ const resp = await fetch(`${url}/feedback`, {
374
+ method: 'POST',
375
+ headers: { Authorization: `Bearer ${authToken}` },
376
+ body: formData,
377
+ });
378
+ if (resp.ok) {
379
+ addOutput('screenshot captured & sent');
380
+ }
381
+ else {
382
+ addOutput(`screenshot upload err: ${resp.status}`);
383
+ }
384
+ }
385
+ // Also create a task so the agent investigates
386
+ const resp = await fetch(`${url}/tasks`, {
387
+ method: 'POST',
388
+ headers: { Authorization: `Bearer ${authToken}`, 'Content-Type': 'application/json' },
389
+ body: JSON.stringify({
390
+ title: 'Bug report from device — investigate the attached screenshot and fix any visible issues.',
391
+ source: 'feedback-sdk',
392
+ description: '[Feedback SDK] User tapped the bug report button. A screenshot of the current screen was captured and sent. Investigate the UI state and fix any issues.',
393
+ hasScreenshot: !!screenshotUri,
394
+ }),
395
+ });
396
+ if (resp.ok) {
397
+ const data = await resp.json();
398
+ const taskId = data.taskId ?? data.id ?? data.task?.id;
399
+ addOutput(taskId ? `bug task ${taskId} created` : 'bug report sent');
400
+ BlackBox_1.BlackBox.log('Bug report submitted', 'FloatingButton');
401
+ }
402
+ else {
403
+ addOutput(`err: ${resp.status}`);
404
+ }
405
+ }
406
+ catch (e) {
407
+ addOutput(`fail: ${String(e).slice(0, 50)}`);
408
+ }
409
+ finally {
410
+ setSending(false);
411
+ }
412
+ }, [agentUrl, authToken, addOutput]);
413
+ // Start autonomous test session
414
+ const handleTestApp = (0, react_1.useCallback)(async () => {
415
+ if (!agentUrl || !authToken)
416
+ return;
417
+ const isRunning = testSession?.active;
418
+ if (isRunning) {
419
+ // Stop test session
420
+ addOutput('> stopping test...');
421
+ try {
422
+ await fetch(`${agentUrl.replace(/\/$/, '')}/test-app/stop`, {
423
+ method: 'POST',
424
+ headers: { Authorization: `Bearer ${authToken}` },
425
+ });
426
+ addOutput('test session stopped.');
427
+ if (testPollRef.current)
428
+ clearInterval(testPollRef.current);
429
+ testPollRef.current = null;
430
+ // Fetch final report
431
+ try {
432
+ const resp = await fetch(`${agentUrl.replace(/\/$/, '')}/test-app/status`, {
433
+ headers: { Authorization: `Bearer ${authToken}` },
434
+ });
435
+ if (resp.ok) {
436
+ const session = await resp.json();
437
+ setTestSession(session);
438
+ if (session.fixes.length > 0) {
439
+ addOutput(`${session.fixes.length} fixes applied (not committed).`);
440
+ addOutput('tap "fixes" to view report.');
441
+ setShowFixReport(true);
442
+ }
443
+ }
444
+ }
445
+ catch { }
446
+ }
447
+ catch (e) {
448
+ addOutput(`fail: ${String(e).slice(0, 50)}`);
449
+ }
450
+ return;
451
+ }
452
+ // Start test session
453
+ addOutput('> starting autonomous test...');
454
+ setSending(true);
455
+ try {
456
+ const url = agentUrl.replace(/\/$/, '');
457
+ const resp = await fetch(`${url}/test-app/start`, {
458
+ method: 'POST',
459
+ headers: { Authorization: `Bearer ${authToken}`, 'Content-Type': 'application/json' },
460
+ body: JSON.stringify({ source: 'feedback-sdk' }),
461
+ });
462
+ if (!resp.ok) {
463
+ addOutput(`err: ${resp.status}`);
464
+ setSending(false);
465
+ return;
466
+ }
467
+ const data = await resp.json();
468
+ addOutput(`test session ${data.sessionId ?? 'started'}...`);
469
+ addOutput('agent reading codebase for context...');
470
+ BlackBox_1.BlackBox.log('Test session started', 'FloatingButton');
471
+ // Poll test status every 3s
472
+ testPollRef.current = setInterval(async () => {
473
+ try {
474
+ const sr = await fetch(`${url}/test-app/status`, {
475
+ headers: { Authorization: `Bearer ${authToken}` },
476
+ });
477
+ if (sr.ok) {
478
+ const session = await sr.json();
479
+ setTestSession(session);
480
+ if (!session.active && testPollRef.current) {
481
+ clearInterval(testPollRef.current);
482
+ testPollRef.current = null;
483
+ addOutput(`test complete: ${session.errorsFound} errors, ${session.fixes.length} fixes.`);
484
+ if (session.fixes.length > 0) {
485
+ addOutput('tap "fixes" to view report.');
486
+ }
487
+ setSending(false);
488
+ }
489
+ }
490
+ }
491
+ catch { }
492
+ }, 3000);
493
+ }
494
+ catch (e) {
495
+ addOutput(`fail: ${String(e).slice(0, 50)}`);
496
+ setSending(false);
497
+ }
498
+ }, [agentUrl, authToken, addOutput, testSession]);
499
+ // Cleanup test poll on unmount
500
+ (0, react_1.useEffect)(() => {
501
+ return () => {
502
+ if (testPollRef.current)
503
+ clearInterval(testPollRef.current);
504
+ };
505
+ }, []);
506
+ const handleDisable = (0, react_1.useCallback)(() => {
507
+ YaverFeedback_1.YaverFeedback.setEnabled(false);
508
+ setChatOpen(false);
509
+ }, []);
510
+ // Auto-scroll output
511
+ (0, react_1.useEffect)(() => {
512
+ if (outputScrollRef.current) {
513
+ setTimeout(() => outputScrollRef.current?.scrollToEnd({ animated: true }), 50);
514
+ }
515
+ }, [output]);
516
+ const isTerminal = stylePreset === 'terminal';
517
+ const buttonIcon = icon ?? 'y';
518
+ const btnBg = isConnected ? color : `${color}88`;
519
+ const panelWidth = fullSize ? screenWidth - 24 : 280;
520
+ return (<react_native_1.Animated.View style={[s.root, { transform: [{ translateX: pan.x }, { translateY: pan.y }] }]} {...panResponder.panHandlers}>
521
+ {/* Console panel */}
522
+ {chatOpen && (<react_native_1.View style={[
523
+ s.panel,
524
+ isTerminal ? s.panelTerminal : s.panelMinimal,
525
+ { borderColor: `${color}44`, width: panelWidth, backgroundColor: panelBg },
526
+ fullSize && { position: 'absolute', left: 0, top: size + 8 },
527
+ ]}>
528
+ {/* Header */}
529
+ <react_native_1.View style={s.headerRow}>
530
+ <react_native_1.Text style={[s.headerTitle, isTerminal && s.mono, { color }]}>
531
+ {isTerminal ? 'YAVER DEBUG' : 'Yaver'}
532
+ </react_native_1.Text>
533
+ <react_native_1.View style={[s.dotSmall, isConnected ? s.green : s.red]}/>
534
+ <react_native_1.Text style={[s.headerStatus, isTerminal && s.mono]}>
535
+ {isConnected ? 'live' : 'off'}
536
+ </react_native_1.Text>
537
+ <react_native_1.TouchableOpacity onPress={() => setFullSize(!fullSize)} style={s.xBtn}>
538
+ <react_native_1.Text style={s.xBtnText}>{fullSize ? '\u25A1' : '\u2197'}</react_native_1.Text>
539
+ </react_native_1.TouchableOpacity>
540
+ <react_native_1.TouchableOpacity onPress={() => { setChatOpen(false); setFullSize(false); }} style={s.xBtn}>
541
+ <react_native_1.Text style={s.xBtnText}>{'\u2715'}</react_native_1.Text>
542
+ </react_native_1.TouchableOpacity>
543
+ </react_native_1.View>
544
+
545
+ {/* Output area */}
546
+ <react_native_1.ScrollView ref={outputScrollRef} style={[s.outputArea, fullSize && s.outputAreaFull]} contentContainerStyle={s.outputContent}>
547
+ {output.length > 0 ? output.map((line, i) => (<react_native_1.Text key={i} style={[
548
+ s.outputLine,
549
+ isTerminal && s.mono,
550
+ fullSize && s.outputLineFull,
551
+ line.startsWith('>')
552
+ ? s.outputLineUser
553
+ : line === 'done.' || line.startsWith('task ')
554
+ ? s.outputLineStatus
555
+ : s.outputLineAgent,
556
+ ]}>
557
+ {line}
558
+ </react_native_1.Text>)) : (<react_native_1.Text style={[s.outputLine, isTerminal && s.mono, { color: '#666' }]}>
559
+ {isConnected ? 'connected. type a message or use actions below.' : 'not connected to agent.'}
560
+ </react_native_1.Text>)}
561
+ {sending && <react_native_1.ActivityIndicator color={color} size="small" style={{ marginTop: 4 }}/>}
562
+ </react_native_1.ScrollView>
563
+
564
+ {/* Input */}
565
+ <react_native_1.View style={s.inputRow}>
566
+ {isTerminal && <react_native_1.Text style={[s.prompt, { color }]}>&gt;</react_native_1.Text>}
567
+ <react_native_1.TextInput style={[s.input, isTerminal && s.mono, fullSize && s.inputFull]} placeholder={isTerminal ? 'tell the agent...' : 'Type a message...'} placeholderTextColor="#444" value={message} onChangeText={setMessage} onSubmitEditing={handleSend} returnKeyType="send" multiline={fullSize}/>
568
+ <react_native_1.TouchableOpacity style={[s.goBtn, { backgroundColor: color }, (sending || !message.trim()) && s.dim]} onPress={handleSend} disabled={sending || !message.trim() || !isConnected}>
569
+ {sending ? (<react_native_1.ActivityIndicator color="#fff" size="small"/>) : (<react_native_1.Text style={[s.goBtnText, isTerminal && s.mono]}>
570
+ {isTerminal ? 'run' : 'Send'}
571
+ </react_native_1.Text>)}
572
+ </react_native_1.TouchableOpacity>
573
+ </react_native_1.View>
574
+
575
+ {/* Action cards row 1 — Reload | Build | Bug */}
576
+ <react_native_1.View style={s.cardRow}>
577
+ <react_native_1.TouchableOpacity style={[s.card, fullSize && s.cardFull, !isConnected && s.dim]} onPress={handleReload} disabled={sending || !isConnected}>
578
+ <react_native_1.Text style={[s.cardIcon, { color: '#fbbf24' }]}>{'\u21BB'}</react_native_1.Text>
579
+ <react_native_1.Text style={[s.cardLabel, isTerminal && s.mono]}>Hot Reload</react_native_1.Text>
580
+ </react_native_1.TouchableOpacity>
581
+ <react_native_1.TouchableOpacity style={[s.card, fullSize && s.cardFull, !isConnected && s.dim]} onPress={handleBuild} disabled={sending || !isConnected}>
582
+ <react_native_1.Text style={[s.cardIcon, { color: '#60a5fa' }]}>{'\u2692'}</react_native_1.Text>
583
+ <react_native_1.Text style={[s.cardLabel, isTerminal && s.mono]}>
584
+ {buildPlatforms === 'both' ? 'Build' : `Build ${buildPlatforms}`}
585
+ </react_native_1.Text>
586
+ {autoDeploy && (<react_native_1.Text style={[s.cardSub, isTerminal && s.mono]}>
587
+ {buildPlatforms === 'ios' ? '+ TestFlight'
588
+ : buildPlatforms === 'android' ? '+ Play Store'
589
+ : buildPlatforms === 'both' ? '+ Deploy'
590
+ : ''}
591
+ </react_native_1.Text>)}
592
+ </react_native_1.TouchableOpacity>
593
+ <react_native_1.TouchableOpacity style={[s.card, fullSize && s.cardFull, !isConnected && s.dim]} onPress={handleBugReport} disabled={sending || !isConnected}>
594
+ <react_native_1.Text style={[s.cardIcon, { color: '#f87171' }]}>{'\u{1F41B}'}</react_native_1.Text>
595
+ <react_native_1.Text style={[s.cardLabel, isTerminal && s.mono]}>Report Bug</react_native_1.Text>
596
+ </react_native_1.TouchableOpacity>
597
+ </react_native_1.View>
598
+
599
+ {/* Action cards row 2 — Test | Fixes */}
600
+ <react_native_1.View style={[s.cardRow, { marginTop: -2 }]}>
601
+ <react_native_1.TouchableOpacity style={[s.card, fullSize && s.cardFull, !isConnected && s.dim,
602
+ testSession?.active && { borderColor: '#a78bfa44', backgroundColor: '#a78bfa08' }]} onPress={handleTestApp} disabled={!isConnected}>
603
+ <react_native_1.Text style={[s.cardIcon, { color: '#a78bfa' }]}>
604
+ {testSession?.active ? '\u23F8' : '\u25B6'}
605
+ </react_native_1.Text>
606
+ <react_native_1.Text style={[s.cardLabel, isTerminal && s.mono]}>
607
+ {testSession?.active ? 'Stop Test' : 'Test App'}
608
+ </react_native_1.Text>
609
+ {testSession?.active && (<react_native_1.Text style={[s.cardSub, isTerminal && s.mono]}>
610
+ {testSession.screensTested}/{testSession.screensDiscovered}
611
+ </react_native_1.Text>)}
612
+ </react_native_1.TouchableOpacity>
613
+ {testSession && testSession.fixes.length > 0 && (<react_native_1.TouchableOpacity style={[s.card, fullSize && s.cardFull]} onPress={() => setShowFixReport(true)}>
614
+ <react_native_1.Text style={[s.cardIcon, { color: '#22c55e' }]}>{'\u2713'}</react_native_1.Text>
615
+ <react_native_1.Text style={[s.cardLabel, isTerminal && s.mono]}>
616
+ {testSession.fixes.length} Fixes
617
+ </react_native_1.Text>
618
+ </react_native_1.TouchableOpacity>)}
619
+ </react_native_1.View>
620
+
621
+ {/* Bottom row */}
622
+ <react_native_1.View style={s.actionsRow}>
623
+ <react_native_1.TouchableOpacity style={s.actionBtn} onPress={() => setOutput([])}>
624
+ <react_native_1.Text style={[s.actionText, isTerminal && s.mono]}>clear</react_native_1.Text>
625
+ </react_native_1.TouchableOpacity>
626
+ <react_native_1.TouchableOpacity style={s.actionBtn} onPress={() => {
627
+ setChatOpen(false);
628
+ YaverFeedback_1.YaverFeedback.startReport();
629
+ }}>
630
+ <react_native_1.Text style={[s.actionText, isTerminal && s.mono]}>report</react_native_1.Text>
631
+ </react_native_1.TouchableOpacity>
632
+ <react_native_1.TouchableOpacity style={s.actionBtn} onPress={handleDisable}>
633
+ <react_native_1.Text style={[s.actionText, isTerminal && s.mono, { color: '#f87171' }]}>quit</react_native_1.Text>
634
+ </react_native_1.TouchableOpacity>
635
+ </react_native_1.View>
636
+ </react_native_1.View>)}
637
+
638
+ {/* Fix Report Modal */}
639
+ <FixReport_1.FixReport session={testSession} visible={showFixReport} onClose={() => setShowFixReport(false)} color={color}/>
640
+
641
+ {/* The button */}
642
+ <react_native_1.TouchableOpacity style={[
643
+ s.button,
644
+ isTerminal ? s.buttonTerminal : s.buttonMinimal,
645
+ { backgroundColor: btnBg, width: size, height: size },
646
+ !isTerminal && { borderRadius: size / 2 },
647
+ ]} activeOpacity={0.7} onPress={handleTap}>
648
+ <react_native_1.Text style={[s.buttonIcon, isTerminal && s.mono, { fontSize: 22 }]}>
649
+ {chatOpen ? '\u2715' : buttonIcon}
650
+ </react_native_1.Text>
651
+ {showStatusDot && (<react_native_1.View style={[s.statusDot, isConnected ? s.green : s.red]}/>)}
652
+ </react_native_1.TouchableOpacity>
653
+ </react_native_1.Animated.View>);
654
+ };
655
+ exports.FloatingButton = FloatingButton;
656
+ const s = react_native_1.StyleSheet.create({
657
+ root: { position: 'absolute', zIndex: 99999, alignItems: 'flex-start' },
658
+ mono: { fontFamily: 'Courier' },
659
+ // Button variants
660
+ button: {
661
+ alignItems: 'center',
662
+ justifyContent: 'center',
663
+ shadowColor: '#000',
664
+ shadowOffset: { width: 0, height: 3 },
665
+ shadowOpacity: 0.5,
666
+ shadowRadius: 5,
667
+ elevation: 10,
668
+ },
669
+ buttonTerminal: { borderRadius: 10 },
670
+ buttonMinimal: { /* borderRadius set inline */},
671
+ buttonIcon: { color: '#fff', fontWeight: '800', fontStyle: 'italic' },
672
+ statusDot: {
673
+ position: 'absolute',
674
+ top: -2,
675
+ right: -2,
676
+ width: 9,
677
+ height: 9,
678
+ borderRadius: 5,
679
+ borderWidth: 1.5,
680
+ borderColor: '#000',
681
+ },
682
+ green: { backgroundColor: '#22c55e' },
683
+ red: { backgroundColor: '#ef4444' },
684
+ // Panel
685
+ panel: {
686
+ padding: 10,
687
+ marginBottom: 6,
688
+ borderWidth: 1,
689
+ shadowColor: '#000',
690
+ shadowOffset: { width: 0, height: 4 },
691
+ shadowOpacity: 0.5,
692
+ shadowRadius: 8,
693
+ elevation: 12,
694
+ },
695
+ panelTerminal: {
696
+ borderRadius: 12,
697
+ },
698
+ panelMinimal: {
699
+ borderRadius: 16,
700
+ },
701
+ // Header
702
+ headerRow: { flexDirection: 'row', alignItems: 'center', marginBottom: 6, gap: 5 },
703
+ headerTitle: { flex: 1, fontSize: 11, fontWeight: '700', textTransform: 'uppercase', letterSpacing: 1 },
704
+ dotSmall: { width: 6, height: 6, borderRadius: 3 },
705
+ headerStatus: { fontSize: 10, color: '#666' },
706
+ xBtn: { paddingHorizontal: 6, paddingVertical: 2 },
707
+ xBtnText: { color: '#666', fontSize: 12 },
708
+ // Output
709
+ outputArea: {
710
+ backgroundColor: '#1a1a1a',
711
+ borderRadius: 8,
712
+ padding: 8,
713
+ marginBottom: 6,
714
+ maxHeight: 140,
715
+ },
716
+ outputAreaFull: { maxHeight: 300, minHeight: 160 },
717
+ outputContent: { paddingBottom: 4 },
718
+ outputLine: {
719
+ fontSize: 11,
720
+ lineHeight: 16,
721
+ },
722
+ outputLineUser: {
723
+ color: '#9ca3af',
724
+ fontStyle: 'italic',
725
+ },
726
+ outputLineAgent: {
727
+ color: '#e5e5e5',
728
+ },
729
+ outputLineStatus: {
730
+ color: '#22c55e',
731
+ },
732
+ outputLineFull: { fontSize: 13, lineHeight: 20 },
733
+ // Input
734
+ inputRow: { flexDirection: 'row', alignItems: 'center', gap: 4, marginBottom: 6 },
735
+ prompt: { fontSize: 14, fontWeight: '700' },
736
+ input: {
737
+ flex: 1,
738
+ backgroundColor: '#1a1a1a',
739
+ borderRadius: 6,
740
+ paddingHorizontal: 8,
741
+ paddingVertical: 6,
742
+ color: '#e5e5e5',
743
+ fontSize: 12,
744
+ borderWidth: 1,
745
+ borderColor: '#3a3a3a',
746
+ },
747
+ inputFull: { fontSize: 15, paddingVertical: 10 },
748
+ goBtn: { borderRadius: 6, paddingHorizontal: 10, paddingVertical: 6 },
749
+ goBtnText: { color: '#fff', fontSize: 11, fontWeight: '700' },
750
+ dim: { opacity: 0.3 },
751
+ // Action cards
752
+ cardRow: { flexDirection: 'row', gap: 6, marginBottom: 6 },
753
+ card: {
754
+ flex: 1,
755
+ backgroundColor: '#1a1a1a',
756
+ borderRadius: 8,
757
+ paddingVertical: 10,
758
+ alignItems: 'center',
759
+ borderWidth: 1,
760
+ borderColor: '#3a3a3a',
761
+ },
762
+ cardFull: { paddingVertical: 14 },
763
+ cardIcon: { fontSize: 18, marginBottom: 2 },
764
+ cardLabel: { fontSize: 10, color: '#999', fontWeight: '600' },
765
+ cardSub: { fontSize: 8, color: '#555', marginTop: 1 },
766
+ // Bottom actions
767
+ actionsRow: { flexDirection: 'row', gap: 4 },
768
+ actionBtn: {
769
+ flex: 1,
770
+ paddingVertical: 5,
771
+ borderRadius: 6,
772
+ alignItems: 'center',
773
+ backgroundColor: '#1a1a1a',
774
+ borderWidth: 1,
775
+ borderColor: '#3a3a3a',
776
+ },
777
+ actionText: { fontSize: 10, color: '#999', fontWeight: '600' },
778
+ });