@mobileai/react-native 0.9.16 → 0.9.18
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/README.md +2 -2
- package/package.json +5 -8
- package/lib/module/__cli_tmp__.js.map +0 -1
- package/lib/module/components/AIAgent.js.map +0 -1
- package/lib/module/components/AIZone.js.map +0 -1
- package/lib/module/components/AgentChatBar.js.map +0 -1
- package/lib/module/components/AgentErrorBoundary.js.map +0 -1
- package/lib/module/components/AgentOverlay.js.map +0 -1
- package/lib/module/components/DiscoveryTooltip.js.map +0 -1
- package/lib/module/components/HighlightOverlay.js.map +0 -1
- package/lib/module/components/Icons.js.map +0 -1
- package/lib/module/components/ProactiveHint.js.map +0 -1
- package/lib/module/components/cards/InfoCard.js.map +0 -1
- package/lib/module/components/cards/ReviewSummary.js.map +0 -1
- package/lib/module/config/endpoints.js.map +0 -1
- package/lib/module/core/ActionRegistry.js.map +0 -1
- package/lib/module/core/AgentRuntime.js.map +0 -1
- package/lib/module/core/FiberTreeWalker.js.map +0 -1
- package/lib/module/core/IdleDetector.js.map +0 -1
- package/lib/module/core/MCPBridge.js.map +0 -1
- package/lib/module/core/ScreenDehydrator.js.map +0 -1
- package/lib/module/core/ZoneRegistry.js.map +0 -1
- package/lib/module/core/systemPrompt.js.map +0 -1
- package/lib/module/core/types.js.map +0 -1
- package/lib/module/hooks/useAction.js.map +0 -1
- package/lib/module/index.js.map +0 -1
- package/lib/module/plugin/withAppIntents.js.map +0 -1
- package/lib/module/providers/GeminiProvider.js.map +0 -1
- package/lib/module/providers/OpenAIProvider.js.map +0 -1
- package/lib/module/providers/ProviderFactory.js.map +0 -1
- package/lib/module/services/AudioInputService.js.map +0 -1
- package/lib/module/services/AudioOutputService.js.map +0 -1
- package/lib/module/services/KnowledgeBaseService.js.map +0 -1
- package/lib/module/services/VoiceService.js.map +0 -1
- package/lib/module/services/flags/FlagService.js.map +0 -1
- package/lib/module/services/telemetry/MobileAI.js.map +0 -1
- package/lib/module/services/telemetry/PiiScrubber.js.map +0 -1
- package/lib/module/services/telemetry/TelemetryService.js.map +0 -1
- package/lib/module/services/telemetry/TouchAutoCapture.js.map +0 -1
- package/lib/module/services/telemetry/device.js.map +0 -1
- package/lib/module/services/telemetry/deviceMetadata.js.map +0 -1
- package/lib/module/services/telemetry/index.js.map +0 -1
- package/lib/module/services/telemetry/types.js.map +0 -1
- package/lib/module/support/CSATSurvey.js.map +0 -1
- package/lib/module/support/EscalationEventSource.js.map +0 -1
- package/lib/module/support/EscalationSocket.js.map +0 -1
- package/lib/module/support/SupportChatModal.js.map +0 -1
- package/lib/module/support/SupportGreeting.js.map +0 -1
- package/lib/module/support/TicketStore.js.map +0 -1
- package/lib/module/support/escalateTool.js.map +0 -1
- package/lib/module/support/index.js.map +0 -1
- package/lib/module/support/supportPrompt.js.map +0 -1
- package/lib/module/support/types.js.map +0 -1
- package/lib/module/tools/datePickerTool.js.map +0 -1
- package/lib/module/tools/guideTool.js.map +0 -1
- package/lib/module/tools/index.js.map +0 -1
- package/lib/module/tools/keyboardTool.js.map +0 -1
- package/lib/module/tools/longPressTool.js.map +0 -1
- package/lib/module/tools/pickerTool.js.map +0 -1
- package/lib/module/tools/restoreTool.js.map +0 -1
- package/lib/module/tools/scrollTool.js.map +0 -1
- package/lib/module/tools/simplifyTool.js.map +0 -1
- package/lib/module/tools/sliderTool.js.map +0 -1
- package/lib/module/tools/tapTool.js.map +0 -1
- package/lib/module/tools/typeTool.js.map +0 -1
- package/lib/module/tools/types.js.map +0 -1
- package/lib/module/types/jsx.d.js.map +0 -1
- package/lib/module/utils/audioUtils.js.map +0 -1
- package/lib/module/utils/logger.js.map +0 -1
- package/lib/typescript/babel.config.d.ts.map +0 -1
- package/lib/typescript/bin/generate-map.d.cts.map +0 -1
- package/lib/typescript/eslint.config.d.mts.map +0 -1
- package/lib/typescript/generate-map.d.ts.map +0 -1
- package/lib/typescript/src/__cli_tmp__.d.ts.map +0 -1
- package/lib/typescript/src/components/AIAgent.d.ts.map +0 -1
- package/lib/typescript/src/components/AIZone.d.ts.map +0 -1
- package/lib/typescript/src/components/AgentChatBar.d.ts.map +0 -1
- package/lib/typescript/src/components/AgentErrorBoundary.d.ts.map +0 -1
- package/lib/typescript/src/components/AgentOverlay.d.ts.map +0 -1
- package/lib/typescript/src/components/DiscoveryTooltip.d.ts.map +0 -1
- package/lib/typescript/src/components/HighlightOverlay.d.ts.map +0 -1
- package/lib/typescript/src/components/Icons.d.ts.map +0 -1
- package/lib/typescript/src/components/ProactiveHint.d.ts.map +0 -1
- package/lib/typescript/src/components/cards/InfoCard.d.ts.map +0 -1
- package/lib/typescript/src/components/cards/ReviewSummary.d.ts.map +0 -1
- package/lib/typescript/src/config/endpoints.d.ts.map +0 -1
- package/lib/typescript/src/core/ActionRegistry.d.ts.map +0 -1
- package/lib/typescript/src/core/AgentRuntime.d.ts.map +0 -1
- package/lib/typescript/src/core/FiberTreeWalker.d.ts.map +0 -1
- package/lib/typescript/src/core/IdleDetector.d.ts.map +0 -1
- package/lib/typescript/src/core/MCPBridge.d.ts.map +0 -1
- package/lib/typescript/src/core/ScreenDehydrator.d.ts.map +0 -1
- package/lib/typescript/src/core/ZoneRegistry.d.ts.map +0 -1
- package/lib/typescript/src/core/systemPrompt.d.ts.map +0 -1
- package/lib/typescript/src/core/types.d.ts.map +0 -1
- package/lib/typescript/src/hooks/useAction.d.ts.map +0 -1
- package/lib/typescript/src/index.d.ts.map +0 -1
- package/lib/typescript/src/plugin/withAppIntents.d.ts.map +0 -1
- package/lib/typescript/src/providers/GeminiProvider.d.ts.map +0 -1
- package/lib/typescript/src/providers/OpenAIProvider.d.ts.map +0 -1
- package/lib/typescript/src/providers/ProviderFactory.d.ts.map +0 -1
- package/lib/typescript/src/services/AudioInputService.d.ts.map +0 -1
- package/lib/typescript/src/services/AudioOutputService.d.ts.map +0 -1
- package/lib/typescript/src/services/KnowledgeBaseService.d.ts.map +0 -1
- package/lib/typescript/src/services/VoiceService.d.ts.map +0 -1
- package/lib/typescript/src/services/flags/FlagService.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/MobileAI.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/PiiScrubber.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/TelemetryService.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/TouchAutoCapture.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/device.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/deviceMetadata.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/index.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/types.d.ts.map +0 -1
- package/lib/typescript/src/support/CSATSurvey.d.ts.map +0 -1
- package/lib/typescript/src/support/EscalationEventSource.d.ts.map +0 -1
- package/lib/typescript/src/support/EscalationSocket.d.ts.map +0 -1
- package/lib/typescript/src/support/SupportChatModal.d.ts.map +0 -1
- package/lib/typescript/src/support/SupportGreeting.d.ts.map +0 -1
- package/lib/typescript/src/support/TicketStore.d.ts.map +0 -1
- package/lib/typescript/src/support/escalateTool.d.ts.map +0 -1
- package/lib/typescript/src/support/index.d.ts.map +0 -1
- package/lib/typescript/src/support/supportPrompt.d.ts.map +0 -1
- package/lib/typescript/src/support/types.d.ts.map +0 -1
- package/lib/typescript/src/tools/datePickerTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/guideTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/index.d.ts.map +0 -1
- package/lib/typescript/src/tools/keyboardTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/longPressTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/pickerTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/restoreTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/scrollTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/simplifyTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/sliderTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/tapTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/typeTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/types.d.ts.map +0 -1
- package/lib/typescript/src/utils/audioUtils.d.ts.map +0 -1
- package/lib/typescript/src/utils/logger.d.ts.map +0 -1
- package/src/__cli_tmp__.tsx +0 -9
- package/src/cli/analyzers/chain-analyzer.ts +0 -183
- package/src/cli/extractors/ai-extractor.ts +0 -6
- package/src/cli/extractors/ast-extractor.ts +0 -551
- package/src/cli/generate-intents.ts +0 -140
- package/src/cli/generate-map.ts +0 -121
- package/src/cli/generate-swift.ts +0 -116
- package/src/cli/scanners/expo-scanner.ts +0 -203
- package/src/cli/scanners/rn-scanner.ts +0 -445
- package/src/components/AIAgent.tsx +0 -1716
- package/src/components/AIZone.tsx +0 -147
- package/src/components/AgentChatBar.tsx +0 -1143
- package/src/components/AgentErrorBoundary.tsx +0 -78
- package/src/components/AgentOverlay.tsx +0 -73
- package/src/components/DiscoveryTooltip.tsx +0 -148
- package/src/components/HighlightOverlay.tsx +0 -136
- package/src/components/Icons.tsx +0 -253
- package/src/components/ProactiveHint.tsx +0 -145
- package/src/components/cards/InfoCard.tsx +0 -58
- package/src/components/cards/ReviewSummary.tsx +0 -76
- package/src/config/endpoints.ts +0 -22
- package/src/core/ActionRegistry.ts +0 -105
- package/src/core/AgentRuntime.ts +0 -1471
- package/src/core/FiberTreeWalker.ts +0 -930
- package/src/core/IdleDetector.ts +0 -72
- package/src/core/MCPBridge.ts +0 -163
- package/src/core/ScreenDehydrator.ts +0 -53
- package/src/core/ZoneRegistry.ts +0 -44
- package/src/core/systemPrompt.ts +0 -431
- package/src/core/types.ts +0 -521
- package/src/hooks/useAction.ts +0 -182
- package/src/index.ts +0 -83
- package/src/plugin/withAppIntents.ts +0 -98
- package/src/providers/GeminiProvider.ts +0 -357
- package/src/providers/OpenAIProvider.ts +0 -379
- package/src/providers/ProviderFactory.ts +0 -36
- package/src/services/AudioInputService.ts +0 -226
- package/src/services/AudioOutputService.ts +0 -236
- package/src/services/KnowledgeBaseService.ts +0 -156
- package/src/services/VoiceService.ts +0 -451
- package/src/services/flags/FlagService.ts +0 -137
- package/src/services/telemetry/MobileAI.ts +0 -66
- package/src/services/telemetry/PiiScrubber.ts +0 -17
- package/src/services/telemetry/TelemetryService.ts +0 -323
- package/src/services/telemetry/TouchAutoCapture.ts +0 -165
- package/src/services/telemetry/device.ts +0 -93
- package/src/services/telemetry/deviceMetadata.ts +0 -13
- package/src/services/telemetry/index.ts +0 -13
- package/src/services/telemetry/types.ts +0 -75
- package/src/support/CSATSurvey.tsx +0 -304
- package/src/support/EscalationEventSource.ts +0 -190
- package/src/support/EscalationSocket.ts +0 -152
- package/src/support/SupportChatModal.tsx +0 -563
- package/src/support/SupportGreeting.tsx +0 -161
- package/src/support/TicketStore.ts +0 -100
- package/src/support/escalateTool.ts +0 -174
- package/src/support/index.ts +0 -29
- package/src/support/supportPrompt.ts +0 -55
- package/src/support/types.ts +0 -155
- package/src/tools/datePickerTool.ts +0 -60
- package/src/tools/guideTool.ts +0 -76
- package/src/tools/index.ts +0 -20
- package/src/tools/keyboardTool.ts +0 -30
- package/src/tools/longPressTool.ts +0 -61
- package/src/tools/pickerTool.ts +0 -115
- package/src/tools/restoreTool.ts +0 -33
- package/src/tools/scrollTool.ts +0 -156
- package/src/tools/simplifyTool.ts +0 -33
- package/src/tools/sliderTool.ts +0 -65
- package/src/tools/tapTool.ts +0 -93
- package/src/tools/typeTool.ts +0 -113
- package/src/tools/types.ts +0 -58
- package/src/types/jsx.d.ts +0 -20
- package/src/utils/audioUtils.ts +0 -54
- package/src/utils/logger.ts +0 -38
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Keyboard Tool — Dismiss the on-screen keyboard.
|
|
3
|
-
*
|
|
4
|
-
* Pattern from:
|
|
5
|
-
* - Maestro: hideKeyboard
|
|
6
|
-
* - Detox: closeSoftKeyboard
|
|
7
|
-
*
|
|
8
|
-
* Uses React Native's Keyboard.dismiss() API.
|
|
9
|
-
* This is a global action — no element index needed.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { Keyboard } from 'react-native';
|
|
13
|
-
import type { AgentTool } from './types';
|
|
14
|
-
|
|
15
|
-
export function createKeyboardTool(): AgentTool {
|
|
16
|
-
return {
|
|
17
|
-
name: 'dismiss_keyboard',
|
|
18
|
-
description: 'Dismiss the on-screen keyboard. Use after typing into a text input when the keyboard is blocking other elements.',
|
|
19
|
-
parameters: {},
|
|
20
|
-
execute: async () => {
|
|
21
|
-
try {
|
|
22
|
-
Keyboard.dismiss();
|
|
23
|
-
await new Promise(resolve => setTimeout(resolve, 300));
|
|
24
|
-
return '✅ Keyboard dismissed.';
|
|
25
|
-
} catch (error: any) {
|
|
26
|
-
return `❌ Error dismissing keyboard: ${error.message}`;
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
};
|
|
30
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Long Press Tool — Extended press interaction via onLongPress.
|
|
3
|
-
*
|
|
4
|
-
* Pattern from:
|
|
5
|
-
* - Detox: longPress(point, duration)
|
|
6
|
-
* - Maestro: longPress(point)
|
|
7
|
-
*
|
|
8
|
-
* In JS fiber level: calls onLongPress prop (available on Pressable/TouchableOpacity).
|
|
9
|
-
* Bubbles up fiber tree if direct element doesn't have onLongPress.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { walkFiberTree } from '../core/FiberTreeWalker';
|
|
13
|
-
import type { AgentTool, ToolContext } from './types';
|
|
14
|
-
|
|
15
|
-
export function createLongPressTool(context: ToolContext): AgentTool {
|
|
16
|
-
return {
|
|
17
|
-
name: 'long_press',
|
|
18
|
-
description: 'Long-press an interactive element by its index. Use for actions that require a longer touch, such as showing context menus, reordering items, or triggering secondary actions.',
|
|
19
|
-
parameters: {
|
|
20
|
-
index: { type: 'number', description: 'The index of the element to long-press', required: true },
|
|
21
|
-
},
|
|
22
|
-
execute: async (args) => {
|
|
23
|
-
const { interactives: elements } = walkFiberTree(context.getRootRef(), context.getWalkConfig());
|
|
24
|
-
const element = elements.find(el => el.index === args.index);
|
|
25
|
-
if (!element) {
|
|
26
|
-
return `❌ Element with index ${args.index} not found. Available indexes: ${elements.map(e => e.index).join(', ')}`;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Strategy 1: Direct onLongPress
|
|
30
|
-
if (element.props.onLongPress && typeof element.props.onLongPress === 'function') {
|
|
31
|
-
try {
|
|
32
|
-
element.props.onLongPress();
|
|
33
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
34
|
-
return `✅ Long-pressed [${args.index}] "${element.label}"`;
|
|
35
|
-
} catch (error: any) {
|
|
36
|
-
return `❌ Error long-pressing [${args.index}]: ${error.message}`;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Strategy 2: Bubble up fiber tree
|
|
41
|
-
let fiber = element.fiberNode?.return;
|
|
42
|
-
let bubbleDepth = 0;
|
|
43
|
-
while (fiber && bubbleDepth < 5) {
|
|
44
|
-
const parentProps = fiber.memoizedProps || {};
|
|
45
|
-
if (parentProps.onLongPress && typeof parentProps.onLongPress === 'function') {
|
|
46
|
-
try {
|
|
47
|
-
parentProps.onLongPress();
|
|
48
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
49
|
-
return `✅ Long-pressed parent of [${args.index}] "${element.label}"`;
|
|
50
|
-
} catch (error: any) {
|
|
51
|
-
return `❌ Error long-pressing parent of [${args.index}]: ${error.message}`;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
fiber = fiber.return;
|
|
55
|
-
bubbleDepth++;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return `❌ Element [${args.index}] "${element.label}" has no long-press handler. Try using tap instead.`;
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
}
|
package/src/tools/pickerTool.ts
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Picker Tool — Select values from picker/dropdown components.
|
|
3
|
-
*
|
|
4
|
-
* Pattern from Detox: setColumnToValue(column, value)
|
|
5
|
-
*
|
|
6
|
-
* In JS: calls onValueChange(itemValue, itemIndex).
|
|
7
|
-
* Reads available options from props.children (Picker.Item) or props.items array.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { walkFiberTree } from '../core/FiberTreeWalker';
|
|
11
|
-
import type { AgentTool, ToolContext } from './types';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Extract available options from a picker element's props.
|
|
15
|
-
* Handles multiple picker libraries:
|
|
16
|
-
* - RN Picker: children are Picker.Item with { label, value } props
|
|
17
|
-
* - RNPickerSelect: items prop is array of { label, value }
|
|
18
|
-
* - DropDownPicker: items prop is array of { label, value }
|
|
19
|
-
*/
|
|
20
|
-
function extractPickerOptions(element: any): Array<{ label: string; value: any }> {
|
|
21
|
-
const props = element.props || {};
|
|
22
|
-
const options: Array<{ label: string; value: any }> = [];
|
|
23
|
-
|
|
24
|
-
// Pattern 1: items prop (RNPickerSelect, DropDownPicker)
|
|
25
|
-
if (Array.isArray(props.items)) {
|
|
26
|
-
for (const item of props.items) {
|
|
27
|
-
if (item && item.label !== undefined) {
|
|
28
|
-
options.push({ label: String(item.label), value: item.value });
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return options;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Pattern 2: options prop (some custom pickers)
|
|
35
|
-
if (Array.isArray(props.options)) {
|
|
36
|
-
for (const item of props.options) {
|
|
37
|
-
if (typeof item === 'string') {
|
|
38
|
-
options.push({ label: item, value: item });
|
|
39
|
-
} else if (item && item.label !== undefined) {
|
|
40
|
-
options.push({ label: String(item.label), value: item.value });
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return options;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Pattern 3: Fiber children (RN Picker with Picker.Item children)
|
|
47
|
-
const fiberNode = element.fiberNode;
|
|
48
|
-
if (fiberNode?.child) {
|
|
49
|
-
let child = fiberNode.child;
|
|
50
|
-
while (child) {
|
|
51
|
-
const childProps = child.memoizedProps || {};
|
|
52
|
-
if (childProps.label !== undefined && childProps.value !== undefined) {
|
|
53
|
-
options.push({ label: String(childProps.label), value: childProps.value });
|
|
54
|
-
}
|
|
55
|
-
child = child.sibling;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return options;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function createPickerTool(context: ToolContext): AgentTool {
|
|
63
|
-
return {
|
|
64
|
-
name: 'select_picker',
|
|
65
|
-
description: 'Select a value from a picker/dropdown by its index. Provide the exact value string to select.',
|
|
66
|
-
parameters: {
|
|
67
|
-
index: { type: 'number', description: 'The index of the picker element', required: true },
|
|
68
|
-
value: { type: 'string', description: 'The value to select (must match an available option)', required: true },
|
|
69
|
-
},
|
|
70
|
-
execute: async (args) => {
|
|
71
|
-
const { interactives: elements } = walkFiberTree(context.getRootRef(), context.getWalkConfig());
|
|
72
|
-
const element = elements.find(el => el.index === args.index);
|
|
73
|
-
if (!element) {
|
|
74
|
-
return `❌ Element with index ${args.index} not found.`;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const onValueChange = element.props.onValueChange;
|
|
78
|
-
if (!onValueChange || typeof onValueChange !== 'function') {
|
|
79
|
-
return `❌ Element [${args.index}] "${element.label}" is not a picker (no onValueChange handler).`;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Find available options
|
|
83
|
-
const options = extractPickerOptions(element);
|
|
84
|
-
|
|
85
|
-
if (options.length > 0) {
|
|
86
|
-
// Find matching option (case-insensitive)
|
|
87
|
-
const match = options.find(
|
|
88
|
-
opt => String(opt.value) === args.value ||
|
|
89
|
-
opt.label.toLowerCase() === args.value.toLowerCase()
|
|
90
|
-
);
|
|
91
|
-
if (!match) {
|
|
92
|
-
const available = options.map(o => `"${o.label}" (${o.value})`).join(', ');
|
|
93
|
-
return `❌ Value "${args.value}" not found in picker. Available: ${available}`;
|
|
94
|
-
}
|
|
95
|
-
try {
|
|
96
|
-
const matchIndex = options.indexOf(match);
|
|
97
|
-
onValueChange(match.value, matchIndex);
|
|
98
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
99
|
-
return `✅ Selected "${match.label}" in picker [${args.index}] "${element.label}"`;
|
|
100
|
-
} catch (error: any) {
|
|
101
|
-
return `❌ Error selecting picker value: ${error.message}`;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// No options found — try direct value pass
|
|
106
|
-
try {
|
|
107
|
-
onValueChange(args.value, 0);
|
|
108
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
109
|
-
return `✅ Set picker [${args.index}] "${element.label}" to "${args.value}"`;
|
|
110
|
-
} catch (error: any) {
|
|
111
|
-
return `❌ Error setting picker value: ${error.message}`;
|
|
112
|
-
}
|
|
113
|
-
},
|
|
114
|
-
};
|
|
115
|
-
}
|
package/src/tools/restoreTool.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type { ToolDefinition } from '../core/types';
|
|
2
|
-
import { globalZoneRegistry } from '../core/ZoneRegistry';
|
|
3
|
-
import { logger } from '../utils/logger';
|
|
4
|
-
|
|
5
|
-
export function createRestoreTool(): ToolDefinition {
|
|
6
|
-
return {
|
|
7
|
-
name: 'restore_zone',
|
|
8
|
-
description: 'Restore a specific Zone to its default state, reversing any previous simplify_zone or inject_card operations.',
|
|
9
|
-
parameters: {
|
|
10
|
-
zoneId: {
|
|
11
|
-
type: 'string',
|
|
12
|
-
description: 'The ID of the AIZone to restore',
|
|
13
|
-
required: true,
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
execute: async (args) => {
|
|
17
|
-
const zoneId = String(args.zoneId);
|
|
18
|
-
|
|
19
|
-
const zone = globalZoneRegistry.get(zoneId) as any;
|
|
20
|
-
if (!zone) {
|
|
21
|
-
return `❌ Cannot restore zone "${zoneId}": Zone does not exist.`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (zone._controller) {
|
|
25
|
-
zone._controller.restore();
|
|
26
|
-
logger.info('RestoreTool', `Restored zone: ${zoneId}`);
|
|
27
|
-
return `✅ Successfully restored zone "${zoneId}" to its default state.`;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return `❌ Cannot restore zone "${zoneId}": Controller not attached.`;
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
}
|
package/src/tools/scrollTool.ts
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Scroll Tool — Direction-based scrolling with safety checks.
|
|
3
|
-
*
|
|
4
|
-
* Patterns implemented:
|
|
5
|
-
* - Detox: ScrollHelper.perform(direction, amountInDP) — relative scroll
|
|
6
|
-
* - Detox: scrollableProbe.atScrollingEdge() — pre-scroll edge detection
|
|
7
|
-
* - Detox: ScrollToIndexAction.getConstraints() — PagerView rejection
|
|
8
|
-
* - Appium: mobileScrollGesture returns boolean (canScrollMore)
|
|
9
|
-
* - Maestro: hierarchy diff post-scroll (no-op detection)
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { findScrollableContainers } from '../core/FiberTreeWalker';
|
|
13
|
-
import type { AgentTool, ToolContext } from './types';
|
|
14
|
-
|
|
15
|
-
export function createScrollTool(context: ToolContext): AgentTool {
|
|
16
|
-
return {
|
|
17
|
-
name: 'scroll',
|
|
18
|
-
description: 'Scroll the current screen to reveal more content. Use when you need to see items that are not yet visible, e.g. in lazy-loaded lists, long forms, or paginated content. If the screen has multiple scrollable areas, specify containerIndex to target a specific one.',
|
|
19
|
-
parameters: {
|
|
20
|
-
direction: {
|
|
21
|
-
type: 'string',
|
|
22
|
-
description: "Scroll direction: 'down' or 'up'",
|
|
23
|
-
required: true,
|
|
24
|
-
enum: ['down', 'up'],
|
|
25
|
-
},
|
|
26
|
-
amount: {
|
|
27
|
-
type: 'string',
|
|
28
|
-
description: "How far to scroll: 'page' (default, ~one screenful), 'toEnd' (jump to bottom), or 'toStart' (jump to top)",
|
|
29
|
-
required: false,
|
|
30
|
-
enum: ['page', 'toEnd', 'toStart'],
|
|
31
|
-
},
|
|
32
|
-
containerIndex: {
|
|
33
|
-
type: 'number',
|
|
34
|
-
description: 'Index of the scrollable container to scroll (0-based). Use when the screen has multiple scrollable areas. Default: 0 (the main/first scrollable area).',
|
|
35
|
-
required: false,
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
execute: async (args) => {
|
|
39
|
-
const screenName = context.getCurrentScreenName();
|
|
40
|
-
const containers = findScrollableContainers(context.getRootRef(), screenName);
|
|
41
|
-
|
|
42
|
-
if (containers.length === 0) {
|
|
43
|
-
return `❌ No scrollable container found on screen "${screenName}". Content may still be loading — wait and try again.`;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const targetIndex = args.containerIndex ?? 0;
|
|
47
|
-
const container = containers[targetIndex];
|
|
48
|
-
if (!container) {
|
|
49
|
-
const available = containers
|
|
50
|
-
.filter(c => !c.isPagerLike)
|
|
51
|
-
.map(c => `[${c.index}] ${c.label} (${c.componentName})`)
|
|
52
|
-
.join(', ');
|
|
53
|
-
return `❌ Container index ${targetIndex} not found. Available scrollable containers on "${screenName}": ${available}`;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// PagerView/TabView Rejection (Detox: getConstraints() rejects ViewPager)
|
|
57
|
-
if (container.isPagerLike) {
|
|
58
|
-
const scrollableAlts = containers
|
|
59
|
-
.filter(c => !c.isPagerLike)
|
|
60
|
-
.map(c => `[${c.index}] ${c.label} (${c.componentName})`)
|
|
61
|
-
.join(', ');
|
|
62
|
-
const suggestion = scrollableAlts
|
|
63
|
-
? `Try scrolling a content container instead: ${scrollableAlts}`
|
|
64
|
-
: 'Use tap on the tab labels to switch tabs instead of scrolling.';
|
|
65
|
-
return `⚠️ Container [${targetIndex}] "${container.label}" is a ${container.componentName} (tab/page container). ` +
|
|
66
|
-
`Scrolling pager containers directly causes native crashes. ${suggestion}`;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const direction: string = args.direction || 'down';
|
|
70
|
-
const amount: string = args.amount || 'page';
|
|
71
|
-
const scrollRef = container.stateNode;
|
|
72
|
-
|
|
73
|
-
// Edge Detection (Detox: scrollableProbe.atScrollingEdge())
|
|
74
|
-
if (amount === 'page' && typeof scrollRef.scrollToOffset === 'function') {
|
|
75
|
-
const metrics = scrollRef._scrollMetrics;
|
|
76
|
-
if (metrics) {
|
|
77
|
-
const { offset, contentLength, visibleLength } = metrics;
|
|
78
|
-
const isAtBottom = offset + visibleLength >= contentLength - 2;
|
|
79
|
-
const isAtTop = offset <= 0;
|
|
80
|
-
|
|
81
|
-
if (direction === 'down' && isAtBottom) {
|
|
82
|
-
return `⚠️ Already at the bottom of ${container.label} (${container.componentName}). No more content to scroll to. All items are visible.`;
|
|
83
|
-
}
|
|
84
|
-
if (direction === 'up' && isAtTop) {
|
|
85
|
-
return `⚠️ Already at the top of ${container.label} (${container.componentName}). Cannot scroll further up.`;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Track position for no-op detection (Maestro: hierarchy diff)
|
|
91
|
-
const offsetBefore = typeof scrollRef.scrollToOffset === 'function'
|
|
92
|
-
? scrollRef._scrollMetrics?.offset ?? 0
|
|
93
|
-
: 0;
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
if (amount === 'toEnd') {
|
|
97
|
-
if (typeof scrollRef.scrollToEnd === 'function') {
|
|
98
|
-
scrollRef.scrollToEnd({ animated: true });
|
|
99
|
-
} else if (typeof scrollRef.scrollTo === 'function') {
|
|
100
|
-
scrollRef.scrollTo({ y: 999999, animated: true });
|
|
101
|
-
}
|
|
102
|
-
} else if (amount === 'toStart') {
|
|
103
|
-
if (typeof scrollRef.scrollTo === 'function') {
|
|
104
|
-
scrollRef.scrollTo({ y: 0, animated: true });
|
|
105
|
-
} else if (typeof scrollRef.scrollToOffset === 'function') {
|
|
106
|
-
scrollRef.scrollToOffset({ offset: 0, animated: true });
|
|
107
|
-
}
|
|
108
|
-
} else {
|
|
109
|
-
// Direction-based relative scroll (Detox: ScrollHelper, Appium: percent)
|
|
110
|
-
const FALLBACK_PAGE_HEIGHT = 800;
|
|
111
|
-
|
|
112
|
-
if (typeof scrollRef.scrollToOffset === 'function') {
|
|
113
|
-
const metrics = scrollRef._scrollMetrics;
|
|
114
|
-
const currentOffset = metrics?.offset ?? 0;
|
|
115
|
-
const visibleLength = metrics?.visibleLength ?? FALLBACK_PAGE_HEIGHT;
|
|
116
|
-
const scrollAmount = Math.round(visibleLength * 0.8);
|
|
117
|
-
|
|
118
|
-
const newOffset = direction === 'down'
|
|
119
|
-
? currentOffset + scrollAmount
|
|
120
|
-
: Math.max(0, currentOffset - scrollAmount);
|
|
121
|
-
|
|
122
|
-
scrollRef.scrollToOffset({ offset: newOffset, animated: true });
|
|
123
|
-
} else if (typeof scrollRef.scrollTo === 'function') {
|
|
124
|
-
const currentY = scrollRef._nativeRef?.contentOffset?.y ?? 0;
|
|
125
|
-
const scrollAmount = FALLBACK_PAGE_HEIGHT;
|
|
126
|
-
|
|
127
|
-
scrollRef.scrollTo({
|
|
128
|
-
y: direction === 'down'
|
|
129
|
-
? currentY + scrollAmount
|
|
130
|
-
: Math.max(0, currentY - scrollAmount),
|
|
131
|
-
animated: true,
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Wait for scroll animation + onEndReached + lazy load
|
|
137
|
-
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
138
|
-
|
|
139
|
-
// Post-scroll no-op detection
|
|
140
|
-
const offsetAfter = typeof scrollRef.scrollToOffset === 'function'
|
|
141
|
-
? scrollRef._scrollMetrics?.offset ?? 0
|
|
142
|
-
: -1;
|
|
143
|
-
|
|
144
|
-
if (offsetAfter >= 0 && Math.abs(offsetAfter - offsetBefore) < 2) {
|
|
145
|
-
const edge = direction === 'down' ? 'bottom' : 'top';
|
|
146
|
-
return `⚠️ Scroll had no effect — already at the ${edge} of ${container.label}. All visible content has been loaded.`;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const amountLabel = amount === 'toEnd' ? 'to end' : amount === 'toStart' ? 'to start' : `${direction} one page`;
|
|
150
|
-
return `✅ Scrolled ${amountLabel} in ${container.label} (${container.componentName}). Check the updated screen content for newly loaded items.`;
|
|
151
|
-
} catch (error: any) {
|
|
152
|
-
return `❌ Scroll failed: ${error.message}`;
|
|
153
|
-
}
|
|
154
|
-
},
|
|
155
|
-
};
|
|
156
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type { ToolDefinition } from '../core/types';
|
|
2
|
-
import { globalZoneRegistry } from '../core/ZoneRegistry';
|
|
3
|
-
import { logger } from '../utils/logger';
|
|
4
|
-
|
|
5
|
-
export function createSimplifyTool(): ToolDefinition {
|
|
6
|
-
return {
|
|
7
|
-
name: 'simplify_zone',
|
|
8
|
-
description: 'Simplify the UI in a specific Zone by telling it to hide elements marked as aiPriority="low". Use this when a screen zone looks cluttered and you want to reduce cognitive load for the user.',
|
|
9
|
-
parameters: {
|
|
10
|
-
zoneId: {
|
|
11
|
-
type: 'string',
|
|
12
|
-
description: 'The ID of the AIZone you want to simplify (e.g. from the screen layout context)',
|
|
13
|
-
required: true,
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
execute: async (args) => {
|
|
17
|
-
const zoneId = String(args.zoneId);
|
|
18
|
-
|
|
19
|
-
if (!globalZoneRegistry.isActionAllowed(zoneId, 'simplify')) {
|
|
20
|
-
return `❌ Cannot simplify zone "${zoneId}": Zone does not exist or allowSimplify is false.`;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const zone = globalZoneRegistry.get(zoneId) as any;
|
|
24
|
-
if (zone && zone._controller) {
|
|
25
|
-
zone._controller.simplify();
|
|
26
|
-
logger.info('SimplifyTool', `Simplified zone: ${zoneId}`);
|
|
27
|
-
return `✅ Successfully requested simplification for zone "${zoneId}".`;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return `❌ Cannot simplify zone "${zoneId}": Controller not attached. Is the zone currently rendered on screen?`;
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
}
|
package/src/tools/sliderTool.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Slider Tool — Adjust slider/seekbar values.
|
|
3
|
-
*
|
|
4
|
-
* Pattern from Detox:
|
|
5
|
-
* - adjustSliderToPosition(normalizedPosition) — 0.0 to 1.0
|
|
6
|
-
* - UISlider+DetoxUtils: value = normalized * (max - min) + min
|
|
7
|
-
*
|
|
8
|
-
* In JS: calls onValueChange(actualValue) then onSlidingComplete(actualValue).
|
|
9
|
-
* Reads min/max from props (minimumValue/maximumValue).
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { walkFiberTree } from '../core/FiberTreeWalker';
|
|
13
|
-
import type { AgentTool, ToolContext } from './types';
|
|
14
|
-
|
|
15
|
-
export function createSliderTool(context: ToolContext): AgentTool {
|
|
16
|
-
return {
|
|
17
|
-
name: 'adjust_slider',
|
|
18
|
-
description: 'Adjust a slider to a specific position. Use for sliders, seek bars, and range selectors. Value is normalized 0.0 (minimum) to 1.0 (maximum).',
|
|
19
|
-
parameters: {
|
|
20
|
-
index: { type: 'number', description: 'The index of the slider element', required: true },
|
|
21
|
-
value: { type: 'number', description: 'Target position from 0.0 (min) to 1.0 (max)', required: true },
|
|
22
|
-
},
|
|
23
|
-
execute: async (args) => {
|
|
24
|
-
const { interactives: elements } = walkFiberTree(context.getRootRef(), context.getWalkConfig());
|
|
25
|
-
const element = elements.find(el => el.index === args.index);
|
|
26
|
-
if (!element) {
|
|
27
|
-
return `❌ Element with index ${args.index} not found.`;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Validate normalized value
|
|
31
|
-
const normalizedValue = Number(args.value);
|
|
32
|
-
if (!Number.isFinite(normalizedValue) || normalizedValue < 0 || normalizedValue > 1) {
|
|
33
|
-
return `❌ Slider value must be between 0.0 and 1.0, got ${args.value}`;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Find onValueChange (Slider's primary callback)
|
|
37
|
-
const onValueChange = element.props.onValueChange;
|
|
38
|
-
if (!onValueChange || typeof onValueChange !== 'function') {
|
|
39
|
-
return `❌ Element [${args.index}] "${element.label}" is not a slider (no onValueChange handler).`;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Calculate actual value from normalized position
|
|
43
|
-
// Pattern from Detox UISlider+DetoxUtils:
|
|
44
|
-
// actualValue = normalized * (max - min) + min
|
|
45
|
-
const min = element.props.minimumValue ?? 0;
|
|
46
|
-
const max = element.props.maximumValue ?? 1;
|
|
47
|
-
const actualValue = normalizedValue * (max - min) + min;
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
// Call onValueChange (continuous feedback)
|
|
51
|
-
onValueChange(actualValue);
|
|
52
|
-
|
|
53
|
-
// Call onSlidingComplete if available (final value callback)
|
|
54
|
-
if (element.props.onSlidingComplete && typeof element.props.onSlidingComplete === 'function') {
|
|
55
|
-
element.props.onSlidingComplete(actualValue);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
59
|
-
return `✅ Adjusted slider [${args.index}] "${element.label}" to ${Math.round(normalizedValue * 100)}% (value: ${actualValue.toFixed(2)})`;
|
|
60
|
-
} catch (error: any) {
|
|
61
|
-
return `❌ Error adjusting slider: ${error.message}`;
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
}
|
package/src/tools/tapTool.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tap Tool — Universal element interaction via onPress.
|
|
3
|
-
*
|
|
4
|
-
* Strategies (in priority order):
|
|
5
|
-
* 1. Switch → onValueChange (toggle)
|
|
6
|
-
* 2. Direct onPress on element
|
|
7
|
-
* 3. Bubble up fiber tree to find parent onPress (max 5 levels)
|
|
8
|
-
*
|
|
9
|
-
* Includes Maestro-style tap verification:
|
|
10
|
-
* - Captures element count + screen name before tap
|
|
11
|
-
* - Compares after tap to detect dead taps
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { walkFiberTree } from '../core/FiberTreeWalker';
|
|
15
|
-
import type { AgentTool, ToolContext } from './types';
|
|
16
|
-
|
|
17
|
-
export function createTapTool(context: ToolContext): AgentTool {
|
|
18
|
-
return {
|
|
19
|
-
name: 'tap',
|
|
20
|
-
description: 'Tap an interactive element by its index. Works universally on buttons, switches, and custom components.',
|
|
21
|
-
parameters: {
|
|
22
|
-
index: { type: 'number', description: 'The index of the element to tap', required: true },
|
|
23
|
-
},
|
|
24
|
-
execute: async (args) => {
|
|
25
|
-
const walkResult = walkFiberTree(context.getRootRef(), context.getWalkConfig());
|
|
26
|
-
const elements = walkResult.interactives;
|
|
27
|
-
const element = elements.find(el => el.index === args.index);
|
|
28
|
-
if (!element) {
|
|
29
|
-
return `❌ Element with index ${args.index} not found. Available indexes: ${elements.map(e => e.index).join(', ')}`;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Pre-tap snapshot for change detection (Pattern from Maestro: hierarchyBasedTap)
|
|
33
|
-
const elementCountBefore = elements.length;
|
|
34
|
-
const screenBefore = context.getCurrentScreenName();
|
|
35
|
-
|
|
36
|
-
// Strategy 1: Switch → onValueChange
|
|
37
|
-
if (element.type === 'switch' && element.props.onValueChange) {
|
|
38
|
-
try {
|
|
39
|
-
element.props.onValueChange(!element.props.value);
|
|
40
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
41
|
-
return `✅ Toggled [${args.index}] "${element.label}" to ${!element.props.value}`;
|
|
42
|
-
} catch (error: any) {
|
|
43
|
-
return `❌ Error toggling [${args.index}]: ${error.message}`;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Strategy 2: Direct onPress
|
|
48
|
-
if (element.props.onPress) {
|
|
49
|
-
try {
|
|
50
|
-
element.props.onPress();
|
|
51
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
52
|
-
|
|
53
|
-
// Post-tap observation (Maestro pattern: compare hierarchy after tap)
|
|
54
|
-
const postWalk = walkFiberTree(context.getRootRef(), context.getWalkConfig());
|
|
55
|
-
const screenAfter = context.getCurrentScreenName();
|
|
56
|
-
const elementCountAfter = postWalk.interactives.length;
|
|
57
|
-
|
|
58
|
-
if (screenAfter !== screenBefore) {
|
|
59
|
-
return `✅ Tapped [${args.index}] "${element.label}" → navigated to "${screenAfter}"`;
|
|
60
|
-
}
|
|
61
|
-
if (Math.abs(elementCountAfter - elementCountBefore) > 0) {
|
|
62
|
-
return `✅ Tapped [${args.index}] "${element.label}" → screen updated (${elementCountBefore} → ${elementCountAfter} elements)`;
|
|
63
|
-
}
|
|
64
|
-
// Many valid taps (add-to-cart, favorites, API calls) update state
|
|
65
|
-
// without changing visible UI elements. Report success and move on.
|
|
66
|
-
return `✅ Tapped [${args.index}] "${element.label}" → action executed successfully. Proceed to your next step.`;
|
|
67
|
-
} catch (error: any) {
|
|
68
|
-
return `❌ Error tapping [${args.index}]: ${error.message}`;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Strategy 3: Bubble up fiber tree (like RNTL's findEventHandler)
|
|
73
|
-
let fiber = element.fiberNode?.return;
|
|
74
|
-
let bubbleDepth = 0;
|
|
75
|
-
while (fiber && bubbleDepth < 5) {
|
|
76
|
-
const parentProps = fiber.memoizedProps || {};
|
|
77
|
-
if (parentProps.onPress && typeof parentProps.onPress === 'function') {
|
|
78
|
-
try {
|
|
79
|
-
parentProps.onPress();
|
|
80
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
81
|
-
return `✅ Tapped parent of [${args.index}] "${element.label}"`;
|
|
82
|
-
} catch (error: any) {
|
|
83
|
-
return `❌ Error tapping parent of [${args.index}]: ${error.message}`;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
fiber = fiber.return;
|
|
87
|
-
bubbleDepth++;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return `❌ Element [${args.index}] "${element.label}" has no tap handler (no onPress or onValueChange found).`;
|
|
91
|
-
},
|
|
92
|
-
};
|
|
93
|
-
}
|