@appilots/sdk 0.11.1 → 0.13.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/control-transformer.js +69 -0
- package/dist/.build-meta.json +3 -3
- package/dist/{chunk-X4MPSZL3.js → chunk-AJDHZRBN.js} +1180 -487
- package/dist/{chunk-DRXUBLGY.js → chunk-ALJGKORS.js} +8 -8
- package/dist/{chunk-PQZ64CCE.mjs → chunk-HRWBK35Y.mjs} +6007 -527
- package/dist/{chunk-LLH7QCSX.mjs → chunk-L3SY3Q4K.mjs} +1 -1
- package/dist/{chunk-GSHE45YX.mjs → chunk-UYBKXULM.mjs} +903 -223
- package/dist/{chunk-KZ2BLMS4.js → chunk-UZ2D4BVX.js} +6026 -525
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.js +11 -11
- package/dist/hooks/index.mjs +2 -2
- package/dist/{index-C01Spw6F.d.mts → index-CI4jmSoP.d.mts} +136 -549
- package/dist/{index-C01Spw6F.d.ts → index-CI4jmSoP.d.ts} +136 -549
- package/dist/index.d.mts +1472 -76
- package/dist/index.d.ts +1472 -76
- package/dist/index.js +1100 -673
- package/dist/index.mjs +933 -565
- package/dist/navigation/index.d.mts +64 -7
- package/dist/navigation/index.d.ts +64 -7
- package/dist/navigation/index.js +13 -13
- package/dist/navigation/index.mjs +2 -2
- package/metro.d.ts +3 -5
- package/metro.js +46 -5
- package/package.json +16 -2
|
@@ -1,6 +1,261 @@
|
|
|
1
|
-
import { getFiberRoot, appilotsDebugWarn, recordIntrospectionFailure, getCurrentScreen, walkFiber, getActiveRouteNames, clampSnapshotToWireLimits, deriveInteractionElements, rectForNode, toWireElement, attachElementIdsToChoiceGroups, elementRegistry, appilotsDebugLog, getLastWalkDiagnostics, SDK_VERSION, geometryStats, geometryAvailability, getNameManglingProbe, emptyDetectorCounts, getAutoTrackingState, getIntrospectionDiagnostics, getDefaultRegistry, useAppilotsContext, ChatSessionMachine, getCurrentScreenSignature, ActionQueueMachine, getScreenMetadata, setCurrentScreen, useResolvedRegistry, totalDetectorHits,
|
|
2
|
-
import React, { useMemo, useRef, useSyncExternalStore, useEffect, useCallback, useState } from 'react';
|
|
3
|
-
import { Alert } from 'react-native';
|
|
1
|
+
import { createSnapshotDeltaTracker, normalizeAppilotsLocale, getFiberRoot, appilotsDebugWarn, recordIntrospectionFailure, getCurrentScreen, walkFiber, getActiveRouteNames, clampSnapshotToWireLimits, deriveInteractionElements, rectForNode, toWireElement, attachElementIdsToChoiceGroups, elementRegistry, appilotsDebugLog, getLastWalkDiagnostics, SDK_VERSION, geometryStats, geometryAvailability, getNameManglingProbe, emptyDetectorCounts, getAutoTrackingState, getIntrospectionDiagnostics, getGeometryHost, isRectOnScreen, listRegistry, resolveAgentPermissions, getDefaultRegistry, useAppilotsContext, ChatSessionMachine, getCurrentScreenSignature, ActionQueueMachine, getScreenMetadata, setCurrentScreen, useResolvedRegistry, totalDetectorHits, getOpenNativeDialog, componentRegistry, permissionDeniedMessage, probeLoadingState, getLastAnsweredNativeDialog, isListItemPressTarget, waitForLoadingSettle, describeValue, waitForScreenSettle, screenDepartedBaseline, routesBelongToSameFeature, actionPressTargetId, getNavigationStateSnapshot, getNavigationRef, isOptionalStepAutomationFailure, OPTIONAL_STEP_AUTOMATION_HINT, isSourceControlId, resolveTarget, isSwitchLike, sourceControlTarget, rectOfFiber, idLooselyMatches, normalize3, matchScore, FORM_VALIDATION_TEXT_RE, parseStableElementId, shouldSkipSubtree, isAlwaysListContainer, isHeuristicListContainer, findListItems, getTypeName, foldForCompare, isModalFiber, sourceControlId, endTextInputEditing, switchValue, getAllScreens, snapshotShowsOpenCreateForm, isReferenceable, looksDestructiveActionLabel, isGenericSelectValue, SUBMIT_CONTROL_RE, isVisibleValidationText, FORM_WRITE_SUCCESS_TEXT_RE, TOUCHABLE_NAMES } from './chunk-HRWBK35Y.mjs';
|
|
2
|
+
import React, { createContext, useMemo, useContext, useRef, useSyncExternalStore, useEffect, useCallback, useState } from 'react';
|
|
3
|
+
import { Platform, NativeModules, AppState, Alert } from 'react-native';
|
|
4
|
+
|
|
5
|
+
// src/i18n/bundles/pt-BR.ts
|
|
6
|
+
var ptBR = {
|
|
7
|
+
chatTitle: "Assistente Appilots",
|
|
8
|
+
clearButton: "Limpar",
|
|
9
|
+
inputPlaceholder: "Digite uma mensagem...",
|
|
10
|
+
emptyTitle: "Como posso ajudar?",
|
|
11
|
+
emptySubtitle: "Navego, preencho formul\xE1rios e executo a\xE7\xF5es neste app.",
|
|
12
|
+
thinking: "Pensando...",
|
|
13
|
+
statusAnalyzing: "Analisando a tela...",
|
|
14
|
+
statusWaitingApp: "Aguardando o app responder...",
|
|
15
|
+
statusAdjusting: "Ajustando o plano...",
|
|
16
|
+
poweredBy: "Powered by Appilots",
|
|
17
|
+
approve: "Aprovar",
|
|
18
|
+
reject: "Rejeitar",
|
|
19
|
+
cancelledByUser: "Cancelado pelo usu\xE1rio",
|
|
20
|
+
confirmDestructiveTitle: "A\xE7\xE3o destrutiva",
|
|
21
|
+
confirmTitle: "Confirma\xE7\xE3o necess\xE1ria",
|
|
22
|
+
confirmMessage: 'Tem certeza que deseja "{action}"?',
|
|
23
|
+
confirmDestructiveApprove: "Sim, executar",
|
|
24
|
+
confirmApprove: "Confirmar",
|
|
25
|
+
confirmCancel: "Cancelar",
|
|
26
|
+
confirmThisAction: "esta a\xE7\xE3o",
|
|
27
|
+
unknownAction: "A\xE7\xE3o desconhecida",
|
|
28
|
+
somethingWentWrong: "Algo deu errado",
|
|
29
|
+
openChatA11y: "Abrir chat do assistente",
|
|
30
|
+
closeChatA11y: "Fechar chat do assistente",
|
|
31
|
+
stopGeneratingA11y: "Parar gera\xE7\xE3o",
|
|
32
|
+
talkToHumanA11y: "Falar com um atendente humano",
|
|
33
|
+
humanAgentLabel: "Atendente",
|
|
34
|
+
escalationRequested: "Certo! Chamei um atendente humano. Voc\xEA pode continuar usando o app normalmente enquanto isso.",
|
|
35
|
+
escalationConnected: "Um atendente entrou na conversa.",
|
|
36
|
+
escalationResolved: "O atendimento humano foi encerrado. Voltei a te ajudar por aqui.",
|
|
37
|
+
escalationOffer: "Se preferir, posso chamar uma pessoa para ajudar.",
|
|
38
|
+
escalationOfferChip: "Falar com um atendente",
|
|
39
|
+
escalationPendingBanner: "Aguardando um atendente...",
|
|
40
|
+
escalationActiveBanner: "Voc\xEA est\xE1 falando com um atendente",
|
|
41
|
+
pillWorking: "Trabalhando...",
|
|
42
|
+
pillExpandA11y: "Abrir a conversa do assistente",
|
|
43
|
+
pillStopA11y: "Parar o assistente",
|
|
44
|
+
missionPause: "Pausar",
|
|
45
|
+
missionResume: "Retomar tarefa",
|
|
46
|
+
missionCancel: "Cancelar tarefa",
|
|
47
|
+
missionPaused: "Pausada",
|
|
48
|
+
pillDone: "Pronto \u2014 toque para ver",
|
|
49
|
+
micStartA11y: "Segurar para falar",
|
|
50
|
+
micStopA11y: "Soltar para enviar",
|
|
51
|
+
micListening: "Ouvindo..."
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// src/i18n/bundles/en.ts
|
|
55
|
+
var en = {
|
|
56
|
+
chatTitle: "Appilots Assistant",
|
|
57
|
+
clearButton: "Clear",
|
|
58
|
+
inputPlaceholder: "Type a message...",
|
|
59
|
+
emptyTitle: "How can I help?",
|
|
60
|
+
emptySubtitle: "I navigate, fill forms, and run actions in this app.",
|
|
61
|
+
thinking: "Thinking...",
|
|
62
|
+
statusAnalyzing: "Reading the screen...",
|
|
63
|
+
statusWaitingApp: "Waiting for the app...",
|
|
64
|
+
statusAdjusting: "Adjusting the plan...",
|
|
65
|
+
poweredBy: "Powered by Appilots",
|
|
66
|
+
approve: "Approve",
|
|
67
|
+
reject: "Reject",
|
|
68
|
+
cancelledByUser: "Cancelled by user",
|
|
69
|
+
confirmDestructiveTitle: "Destructive action",
|
|
70
|
+
confirmTitle: "Confirmation needed",
|
|
71
|
+
confirmMessage: 'Are you sure you want to "{action}"?',
|
|
72
|
+
confirmDestructiveApprove: "Yes, do it",
|
|
73
|
+
confirmApprove: "Confirm",
|
|
74
|
+
confirmCancel: "Cancel",
|
|
75
|
+
confirmThisAction: "this action",
|
|
76
|
+
unknownAction: "Unknown action",
|
|
77
|
+
somethingWentWrong: "Something went wrong",
|
|
78
|
+
openChatA11y: "Open assistant chat",
|
|
79
|
+
closeChatA11y: "Close assistant chat",
|
|
80
|
+
stopGeneratingA11y: "Stop generating",
|
|
81
|
+
talkToHumanA11y: "Talk to a human agent",
|
|
82
|
+
humanAgentLabel: "Agent",
|
|
83
|
+
escalationRequested: "Done! I've called a human agent. You can keep using the app normally in the meantime.",
|
|
84
|
+
escalationConnected: "A human agent joined the conversation.",
|
|
85
|
+
escalationResolved: "The human handoff has ended. I'm back to help you here.",
|
|
86
|
+
escalationOffer: "If you prefer, I can bring in a person to help.",
|
|
87
|
+
escalationOfferChip: "Talk to a human",
|
|
88
|
+
escalationPendingBanner: "Waiting for a human agent...",
|
|
89
|
+
escalationActiveBanner: "You're talking to a human agent",
|
|
90
|
+
pillWorking: "Working...",
|
|
91
|
+
pillExpandA11y: "Open the assistant conversation",
|
|
92
|
+
pillStopA11y: "Stop the assistant",
|
|
93
|
+
missionPause: "Pause",
|
|
94
|
+
missionResume: "Resume task",
|
|
95
|
+
missionCancel: "Cancel task",
|
|
96
|
+
missionPaused: "Paused",
|
|
97
|
+
pillDone: "Done \u2014 tap to read",
|
|
98
|
+
micStartA11y: "Hold to talk",
|
|
99
|
+
micStopA11y: "Release to send",
|
|
100
|
+
micListening: "Listening..."
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// src/i18n/bundles/es.ts
|
|
104
|
+
var es = {
|
|
105
|
+
chatTitle: "Asistente Appilots",
|
|
106
|
+
clearButton: "Limpiar",
|
|
107
|
+
inputPlaceholder: "Escribe un mensaje...",
|
|
108
|
+
emptyTitle: "\xBFEn qu\xE9 puedo ayudarte?",
|
|
109
|
+
emptySubtitle: "Navego, relleno formularios y ejecuto acciones en esta app.",
|
|
110
|
+
thinking: "Pensando...",
|
|
111
|
+
statusAnalyzing: "Analizando la pantalla...",
|
|
112
|
+
statusWaitingApp: "Esperando a la app...",
|
|
113
|
+
statusAdjusting: "Ajustando el plan...",
|
|
114
|
+
poweredBy: "Hecho con Appilots",
|
|
115
|
+
approve: "Aprobar",
|
|
116
|
+
reject: "Rechazar",
|
|
117
|
+
cancelledByUser: "Cancelado por el usuario",
|
|
118
|
+
confirmDestructiveTitle: "Acci\xF3n destructiva",
|
|
119
|
+
confirmTitle: "Confirmaci\xF3n necesaria",
|
|
120
|
+
confirmMessage: '\xBFSeguro que quieres "{action}"?',
|
|
121
|
+
confirmDestructiveApprove: "S\xED, ejecutar",
|
|
122
|
+
confirmApprove: "Confirmar",
|
|
123
|
+
confirmCancel: "Cancelar",
|
|
124
|
+
confirmThisAction: "esta acci\xF3n",
|
|
125
|
+
unknownAction: "Acci\xF3n desconocida",
|
|
126
|
+
somethingWentWrong: "Algo sali\xF3 mal",
|
|
127
|
+
openChatA11y: "Abrir chat del asistente",
|
|
128
|
+
closeChatA11y: "Cerrar chat del asistente",
|
|
129
|
+
stopGeneratingA11y: "Detener generaci\xF3n",
|
|
130
|
+
talkToHumanA11y: "Hablar con un agente humano",
|
|
131
|
+
humanAgentLabel: "Agente",
|
|
132
|
+
escalationRequested: "\xA1Listo! Llam\xE9 a un agente humano. Mientras tanto puedes seguir usando la app con normalidad.",
|
|
133
|
+
escalationConnected: "Un agente humano se uni\xF3 a la conversaci\xF3n.",
|
|
134
|
+
escalationResolved: "La atenci\xF3n humana termin\xF3. Vuelvo a ayudarte por aqu\xED.",
|
|
135
|
+
escalationOffer: "Si prefieres, puedo llamar a una persona para ayudarte.",
|
|
136
|
+
escalationOfferChip: "Hablar con un agente",
|
|
137
|
+
escalationPendingBanner: "Esperando a un agente...",
|
|
138
|
+
escalationActiveBanner: "Est\xE1s hablando con un agente humano",
|
|
139
|
+
pillWorking: "Trabajando...",
|
|
140
|
+
pillExpandA11y: "Abrir la conversaci\xF3n del asistente",
|
|
141
|
+
pillStopA11y: "Detener el asistente",
|
|
142
|
+
missionPause: "Pausar",
|
|
143
|
+
missionResume: "Reanudar tarea",
|
|
144
|
+
missionCancel: "Cancelar tarea",
|
|
145
|
+
missionPaused: "Pausada",
|
|
146
|
+
pillDone: "Listo \u2014 toca para ver",
|
|
147
|
+
micStartA11y: "Mantener para hablar",
|
|
148
|
+
micStopA11y: "Soltar para enviar",
|
|
149
|
+
micListening: "Escuchando..."
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// src/i18n/bundles/fr.ts
|
|
153
|
+
var fr = {
|
|
154
|
+
chatTitle: "Assistant Appilots",
|
|
155
|
+
clearButton: "Effacer",
|
|
156
|
+
inputPlaceholder: "\xC9crivez un message\u2026",
|
|
157
|
+
emptyTitle: "Comment puis-je vous aider ?",
|
|
158
|
+
emptySubtitle: "Je navigue, remplis les formulaires et effectue des actions dans cette application.",
|
|
159
|
+
thinking: "R\xE9flexion en cours\u2026",
|
|
160
|
+
statusAnalyzing: "Lecture de l\u2019\xE9cran\u2026",
|
|
161
|
+
statusWaitingApp: "En attente de l\u2019application\u2026",
|
|
162
|
+
statusAdjusting: "Ajustement du plan\u2026",
|
|
163
|
+
poweredBy: "Propuls\xE9 par Appilots",
|
|
164
|
+
approve: "Approuver",
|
|
165
|
+
reject: "Refuser",
|
|
166
|
+
cancelledByUser: "Annul\xE9 par l\u2019utilisateur",
|
|
167
|
+
confirmDestructiveTitle: "Action destructive",
|
|
168
|
+
confirmTitle: "Confirmation requise",
|
|
169
|
+
confirmMessage: "Voulez-vous vraiment \xAB {action} \xBB ?",
|
|
170
|
+
confirmDestructiveApprove: "Oui, ex\xE9cuter",
|
|
171
|
+
confirmApprove: "Confirmer",
|
|
172
|
+
confirmCancel: "Annuler",
|
|
173
|
+
confirmThisAction: "cette action",
|
|
174
|
+
unknownAction: "Action inconnue",
|
|
175
|
+
somethingWentWrong: "Une erreur est survenue",
|
|
176
|
+
openChatA11y: "Ouvrir la conversation avec l\u2019assistant",
|
|
177
|
+
closeChatA11y: "Fermer la conversation",
|
|
178
|
+
stopGeneratingA11y: "Arr\xEAter la g\xE9n\xE9ration",
|
|
179
|
+
talkToHumanA11y: "Parler \xE0 un conseiller",
|
|
180
|
+
humanAgentLabel: "Conseiller",
|
|
181
|
+
escalationRequested: "J\u2019ai contact\xE9 un conseiller. Vous pouvez continuer \xE0 utiliser l\u2019application en attendant.",
|
|
182
|
+
escalationConnected: "Un conseiller a rejoint la conversation.",
|
|
183
|
+
escalationResolved: "L\u2019intervention du conseiller est termin\xE9e. Je peux \xE0 nouveau vous aider.",
|
|
184
|
+
escalationOffer: "Si vous pr\xE9f\xE9rez, je peux faire appel \xE0 un conseiller.",
|
|
185
|
+
escalationOfferChip: "Parler \xE0 un conseiller",
|
|
186
|
+
escalationPendingBanner: "En attente d\u2019un conseiller\u2026",
|
|
187
|
+
escalationActiveBanner: "Vous discutez avec un conseiller",
|
|
188
|
+
pillWorking: "En cours\u2026",
|
|
189
|
+
pillExpandA11y: "Ouvrir la conversation avec l\u2019assistant",
|
|
190
|
+
pillStopA11y: "Arr\xEAter l\u2019assistant",
|
|
191
|
+
missionPause: "Mettre en pause",
|
|
192
|
+
missionResume: "Reprendre la t\xE2che",
|
|
193
|
+
missionCancel: "Annuler la t\xE2che",
|
|
194
|
+
missionPaused: "En pause",
|
|
195
|
+
pillDone: "Termin\xE9 \u2014 touchez pour lire",
|
|
196
|
+
micStartA11y: "Maintenez pour parler",
|
|
197
|
+
micStopA11y: "Rel\xE2chez pour envoyer",
|
|
198
|
+
micListening: "\xC9coute en cours\u2026"
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// src/i18n/I18nProvider.tsx
|
|
202
|
+
var BUNDLES = {
|
|
203
|
+
"pt-BR": ptBR,
|
|
204
|
+
en,
|
|
205
|
+
es,
|
|
206
|
+
fr
|
|
207
|
+
};
|
|
208
|
+
var DEFAULT_LOCALE = "en";
|
|
209
|
+
function detectDeviceLocale() {
|
|
210
|
+
try {
|
|
211
|
+
let raw;
|
|
212
|
+
if (Platform.OS === "ios") {
|
|
213
|
+
const manager = NativeModules?.SettingsManager;
|
|
214
|
+
const settings = manager?.getConstants?.()?.settings ?? manager?.settings;
|
|
215
|
+
const languages = Array.isArray(settings?.AppleLanguages) ? settings.AppleLanguages : [];
|
|
216
|
+
for (const language of languages) {
|
|
217
|
+
const locale = normalizeAppilotsLocale(language);
|
|
218
|
+
if (locale) return locale;
|
|
219
|
+
}
|
|
220
|
+
raw = settings?.AppleLocale;
|
|
221
|
+
} else if (Platform.OS === "android") {
|
|
222
|
+
const manager = NativeModules?.I18nManager;
|
|
223
|
+
raw = manager?.getConstants?.()?.localeIdentifier ?? manager?.localeIdentifier;
|
|
224
|
+
}
|
|
225
|
+
if (!raw || typeof raw !== "string") return null;
|
|
226
|
+
return normalizeAppilotsLocale(raw) ?? null;
|
|
227
|
+
} catch {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function resolveLocale(prop) {
|
|
232
|
+
if (prop && BUNDLES[prop]) return prop;
|
|
233
|
+
const detected = detectDeviceLocale();
|
|
234
|
+
if (detected && BUNDLES[detected]) return detected;
|
|
235
|
+
return DEFAULT_LOCALE;
|
|
236
|
+
}
|
|
237
|
+
var I18nContext = createContext(null);
|
|
238
|
+
function AppilotsI18nProvider({ locale, overrides, children }) {
|
|
239
|
+
const value = useMemo(() => {
|
|
240
|
+
const resolved = resolveLocale(locale ?? null);
|
|
241
|
+
const merged = overrides ? { ...BUNDLES[resolved], ...overrides } : BUNDLES[resolved];
|
|
242
|
+
return {
|
|
243
|
+
locale: resolved,
|
|
244
|
+
strings: merged,
|
|
245
|
+
t: (key) => merged[key]
|
|
246
|
+
};
|
|
247
|
+
}, [locale, overrides]);
|
|
248
|
+
return /* @__PURE__ */ React.createElement(I18nContext.Provider, { value }, children);
|
|
249
|
+
}
|
|
250
|
+
function useAppilotsI18n() {
|
|
251
|
+
const ctx = useContext(I18nContext);
|
|
252
|
+
if (ctx) return ctx;
|
|
253
|
+
return {
|
|
254
|
+
locale: DEFAULT_LOCALE,
|
|
255
|
+
strings: BUNDLES[DEFAULT_LOCALE],
|
|
256
|
+
t: (key) => BUNDLES[DEFAULT_LOCALE][key]
|
|
257
|
+
};
|
|
258
|
+
}
|
|
4
259
|
|
|
5
260
|
// src/introspection/captureSnapshot.ts
|
|
6
261
|
function normalizeSliderKey(value) {
|
|
@@ -74,20 +329,35 @@ function mergeRegisteredLists(snapshot) {
|
|
|
74
329
|
}
|
|
75
330
|
for (const entry of registered) {
|
|
76
331
|
if (entry.kind === "scroll") continue;
|
|
332
|
+
const existing = byId.get(entry.id);
|
|
333
|
+
if (!existing && !(entry.screen && entry.screen === snapshot.route)) continue;
|
|
77
334
|
const metrics = entry.getScrollMetrics?.();
|
|
78
335
|
const scrollFacts = metrics ? {
|
|
79
336
|
scrollOffsetY: Math.max(0, Math.round(metrics.offset)),
|
|
80
337
|
canScrollUp: metrics.offset > 8,
|
|
81
338
|
canScrollDown: metrics.offset + metrics.visibleLength < metrics.contentLength - 8
|
|
82
339
|
} : void 0;
|
|
83
|
-
const existing = byId.get(entry.id);
|
|
84
340
|
if (existing) {
|
|
85
341
|
existing.itemCount = existing.itemCount ?? entry.itemCount;
|
|
342
|
+
existing.totalItemCount = existing.totalItemCount ?? entry.totalItemCount;
|
|
86
343
|
existing.refreshing = existing.refreshing ?? entry.refreshing;
|
|
87
344
|
existing.empty = existing.empty ?? entry.empty;
|
|
88
345
|
existing.label = existing.label ?? entry.label;
|
|
89
346
|
existing.source = existing.source ?? "auto-tracked";
|
|
90
347
|
existing.visibleItemCount = existing.items.length;
|
|
348
|
+
const viewport = entry.measureViewport?.();
|
|
349
|
+
if (viewport && viewport.width > 0 && viewport.height > 0) {
|
|
350
|
+
let measured = 0;
|
|
351
|
+
let inViewport = 0;
|
|
352
|
+
for (const item of existing.items) {
|
|
353
|
+
const rect = rectForNode(item);
|
|
354
|
+
if (!rect) continue;
|
|
355
|
+
measured++;
|
|
356
|
+
item.onScreen = rect.width > 0 && rect.height > 0 && rect.x < viewport.x + viewport.width && rect.x + rect.width > viewport.x && rect.y < viewport.y + viewport.height && rect.y + rect.height > viewport.y;
|
|
357
|
+
if (item.onScreen) inViewport++;
|
|
358
|
+
}
|
|
359
|
+
if (measured === existing.items.length) existing.viewportItemCount = inViewport;
|
|
360
|
+
}
|
|
91
361
|
if (entry.dataPreview && entry.dataPreview.length > 0) {
|
|
92
362
|
existing.dataPreview = entry.dataPreview;
|
|
93
363
|
}
|
|
@@ -104,6 +374,7 @@ function mergeRegisteredLists(snapshot) {
|
|
|
104
374
|
containerType: entry.component,
|
|
105
375
|
source: "registry",
|
|
106
376
|
itemCount: entry.itemCount,
|
|
377
|
+
totalItemCount: entry.totalItemCount,
|
|
107
378
|
visibleItemCount: 0,
|
|
108
379
|
refreshing: entry.refreshing,
|
|
109
380
|
empty: entry.empty,
|
|
@@ -169,6 +440,43 @@ function recordBlindWalk() {
|
|
|
169
440
|
if (totalDetectorHits(walk.detectors) > 0) return;
|
|
170
441
|
recordIntrospectionFailure("walk-recognized-nothing", null);
|
|
171
442
|
}
|
|
443
|
+
function mergeRegisteredFieldValues(snapshot) {
|
|
444
|
+
const present = new Set(snapshot.inputs.map((i) => normalizeFieldKey(i.id)));
|
|
445
|
+
const onScreenIds = [
|
|
446
|
+
...snapshot.buttons.map((b) => (b.id ?? "").toLowerCase()),
|
|
447
|
+
...snapshot.toggles.map((t) => (t.id ?? "").toLowerCase())
|
|
448
|
+
].filter(Boolean);
|
|
449
|
+
const onScreenLabels = new Set(
|
|
450
|
+
[...snapshot.buttons.map((b) => b.label), ...snapshot.texts].filter((l) => !!l).map((l) => l.toLowerCase())
|
|
451
|
+
);
|
|
452
|
+
for (const { id, kind, screen, label } of componentRegistry.snapshot()) {
|
|
453
|
+
if (kind !== "field") continue;
|
|
454
|
+
const key = normalizeFieldKey(id);
|
|
455
|
+
if (!key || present.has(key)) continue;
|
|
456
|
+
const entry = componentRegistry.getField(id);
|
|
457
|
+
if (!entry) continue;
|
|
458
|
+
const fieldType = entry.fieldType ?? "";
|
|
459
|
+
if (fieldType === "text" || fieldType === "") continue;
|
|
460
|
+
const declaresRoute = typeof screen === "string" && screen === snapshot.route;
|
|
461
|
+
if (screen && !declaresRoute && !getActiveRouteNames().includes(screen)) continue;
|
|
462
|
+
const idEvidence = onScreenIds.some((candidate) => candidate.split(/[^a-z0-9]+/).includes(key));
|
|
463
|
+
const labelEvidence = !!entry.label && onScreenLabels.has(entry.label.toLowerCase());
|
|
464
|
+
if (!declaresRoute && !idEvidence && !labelEvidence) continue;
|
|
465
|
+
let value;
|
|
466
|
+
try {
|
|
467
|
+
value = String(entry.getValue() ?? "");
|
|
468
|
+
} catch {
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
snapshot.inputs.push({
|
|
472
|
+
id,
|
|
473
|
+
label: entry.label ?? label,
|
|
474
|
+
value,
|
|
475
|
+
type: fieldType
|
|
476
|
+
});
|
|
477
|
+
present.add(key);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
172
480
|
function captureSnapshot() {
|
|
173
481
|
const root = getFiberRoot();
|
|
174
482
|
if (!root) {
|
|
@@ -201,6 +509,7 @@ function captureSnapshot() {
|
|
|
201
509
|
mergeNativeDialog(snapshot);
|
|
202
510
|
reconcileSliders(snapshot);
|
|
203
511
|
canonicalizeRegisteredFields(snapshot);
|
|
512
|
+
mergeRegisteredFieldValues(snapshot);
|
|
204
513
|
clampSnapshotToWireLimits(snapshot);
|
|
205
514
|
const entries = deriveInteractionElements(snapshot, { rectOf: rectForNode });
|
|
206
515
|
const rectById = new Map(
|
|
@@ -303,6 +612,11 @@ function flipSwitchFiber(fiber, next) {
|
|
|
303
612
|
return { ok: true, via: handler.via };
|
|
304
613
|
}
|
|
305
614
|
|
|
615
|
+
// src/introspection/positionalIdentity.ts
|
|
616
|
+
function isPositionalControlId(value) {
|
|
617
|
+
return /^(?:list-\d+-item-\d+-)?(?:btn|input|slider|toggle)-\d+$/i.test(value.trim());
|
|
618
|
+
}
|
|
619
|
+
|
|
306
620
|
// src/introspection/findInteractive.ts
|
|
307
621
|
function getTypeName2(type) {
|
|
308
622
|
if (!type) return null;
|
|
@@ -390,6 +704,11 @@ function shouldSkipSubtree2(fiber) {
|
|
|
390
704
|
var normalize = foldForCompare;
|
|
391
705
|
function scoreMatchDetail(fiber, queryNorm, queryRaw) {
|
|
392
706
|
const props = fiber.memoizedProps ?? {};
|
|
707
|
+
if (isSourceControlId(queryRaw)) {
|
|
708
|
+
const matches = sourceControlId(fiber) === queryRaw;
|
|
709
|
+
const disabled = props.disabled === true || props.accessibilityState?.disabled === true;
|
|
710
|
+
return { score: matches ? disabled ? 60 : 120 : 0, named: matches, disabled };
|
|
711
|
+
}
|
|
393
712
|
const candidates = [];
|
|
394
713
|
if (typeof props.testID === "string") candidates.push({ val: props.testID, weight: 100 });
|
|
395
714
|
if (typeof props.accessibilityLabel === "string")
|
|
@@ -408,11 +727,11 @@ function scoreMatchDetail(fiber, queryNorm, queryRaw) {
|
|
|
408
727
|
if (!vNorm) continue;
|
|
409
728
|
if (val === queryRaw) best = Math.max(best, weight + 20);
|
|
410
729
|
else if (vNorm === queryNorm) best = Math.max(best, weight + 10);
|
|
411
|
-
else if (queryNorm && (vNorm.includes(queryNorm) || queryNorm.includes(vNorm)))
|
|
730
|
+
else if (!isPositionalControlId(queryRaw) && queryNorm && (vNorm.includes(queryNorm) || queryNorm.includes(vNorm)))
|
|
412
731
|
best = Math.max(best, weight - 20);
|
|
413
732
|
}
|
|
414
733
|
const named = best >= IDENTITY_MATCH_SCORE;
|
|
415
|
-
const disabledProp = props.disabled === true;
|
|
734
|
+
const disabledProp = props.disabled === true || props.editable === false || props.readOnly === true;
|
|
416
735
|
const disabledA11y = typeof props.accessibilityState === "object" && props.accessibilityState !== null && props.accessibilityState.disabled === true;
|
|
417
736
|
if (best > 0 && disabledProp) {
|
|
418
737
|
best = Math.max(1, best - 60);
|
|
@@ -422,12 +741,18 @@ function scoreMatchDetail(fiber, queryNorm, queryRaw) {
|
|
|
422
741
|
}
|
|
423
742
|
return { score: best, named, disabled: disabledProp || disabledA11y };
|
|
424
743
|
}
|
|
744
|
+
var inputIdentities = /* @__PURE__ */ new WeakMap();
|
|
425
745
|
function wrapTextInput(fiber) {
|
|
746
|
+
const identity = inputIdentities.get(fiber) ?? (fiber.alternate && inputIdentities.get(fiber.alternate)) ?? {};
|
|
747
|
+
inputIdentities.set(fiber, identity);
|
|
748
|
+
if (fiber.alternate) inputIdentities.set(fiber.alternate, identity);
|
|
426
749
|
const getProps = () => fiber.memoizedProps ?? {};
|
|
427
750
|
return {
|
|
428
751
|
kind: "field",
|
|
752
|
+
controlIdentity: identity,
|
|
429
753
|
label: getProps().accessibilityLabel ?? getProps().label ?? getProps().placeholder ?? getProps().testID,
|
|
430
754
|
getValue: () => getProps().value,
|
|
755
|
+
finishEditing: () => endTextInputEditing(getProps()),
|
|
431
756
|
setValue: (value) => {
|
|
432
757
|
const { onChangeText, onChange } = getProps();
|
|
433
758
|
if (typeof onChangeText === "function") {
|
|
@@ -508,10 +833,10 @@ function describeCandidate(fiber, kind) {
|
|
|
508
833
|
const props = fiber?.memoizedProps ?? {};
|
|
509
834
|
return props.testID ?? props.accessibilityLabel ?? props.placeholder ?? (kind === "target" ? findChildText(fiber) : void 0);
|
|
510
835
|
}
|
|
511
|
-
function isDescendantOf(fiber, ancestor) {
|
|
836
|
+
function isDescendantOf(fiber, ancestor, parents) {
|
|
512
837
|
if (!fiber || !ancestor) return false;
|
|
513
|
-
let node = fiber
|
|
514
|
-
for (let depth = 0; node && depth < 200; depth++, node = node
|
|
838
|
+
let node = parents.get(fiber);
|
|
839
|
+
for (let depth = 0; node && depth < 200; depth++, node = parents.get(node)) {
|
|
515
840
|
if (node === ancestor) return true;
|
|
516
841
|
}
|
|
517
842
|
return false;
|
|
@@ -532,13 +857,15 @@ function findInteractiveByIdentifier(query, options = {}) {
|
|
|
532
857
|
let bestDisabled = false;
|
|
533
858
|
let tied = [];
|
|
534
859
|
const visited = /* @__PURE__ */ new WeakSet();
|
|
860
|
+
const parents = /* @__PURE__ */ new WeakMap();
|
|
535
861
|
const stack = [];
|
|
536
|
-
if (root.child) stack.push(root.child);
|
|
862
|
+
if (root.child) stack.push({ fiber: root.child, parent: root });
|
|
537
863
|
while (stack.length > 0) {
|
|
538
|
-
const fiber = stack.pop();
|
|
864
|
+
const { fiber, parent } = stack.pop();
|
|
539
865
|
if (!fiber || visited.has(fiber)) continue;
|
|
540
866
|
visited.add(fiber);
|
|
541
|
-
|
|
867
|
+
parents.set(fiber, parent);
|
|
868
|
+
if (fiber.sibling) stack.push({ fiber: fiber.sibling, parent });
|
|
542
869
|
if (shouldSkipSubtree2(fiber)) continue;
|
|
543
870
|
const props = fiber.memoizedProps ?? {};
|
|
544
871
|
let kind = null;
|
|
@@ -552,7 +879,7 @@ function findInteractiveByIdentifier(query, options = {}) {
|
|
|
552
879
|
if (score > bestScore) tied = [{ fiber, kind }];
|
|
553
880
|
else if (score === bestScore) tied.push({ fiber, kind });
|
|
554
881
|
}
|
|
555
|
-
const beatsBest = score > bestScore || score > 0 && score === bestScore && isDescendantOf(fiber, bestFiber);
|
|
882
|
+
const beatsBest = score > bestScore || score > 0 && score === bestScore && isDescendantOf(fiber, bestFiber, parents);
|
|
556
883
|
if (beatsBest) {
|
|
557
884
|
bestScore = score;
|
|
558
885
|
bestFiber = fiber;
|
|
@@ -561,11 +888,15 @@ function findInteractiveByIdentifier(query, options = {}) {
|
|
|
561
888
|
bestId = describeCandidate(fiber, kind);
|
|
562
889
|
if (kind === "field") bestHandle = wrapTextInput(fiber);
|
|
563
890
|
else if (kind === "toggle") bestHandle = wrapSwitch(fiber);
|
|
564
|
-
else
|
|
891
|
+
else if (isSourceControlId(queryRaw)) {
|
|
892
|
+
const bound = sourceControlTarget(fiber);
|
|
893
|
+
if (!bound || bound.disabled) bestHandle = null;
|
|
894
|
+
else bestHandle = wrapTouchable(bound.fiber, findChildText(fiber));
|
|
895
|
+
} else bestHandle = wrapTouchable(fiber, findChildText(fiber));
|
|
565
896
|
}
|
|
566
897
|
}
|
|
567
898
|
if (kind === "toggle") continue;
|
|
568
|
-
if (fiber.child) stack.push(fiber.child);
|
|
899
|
+
if (fiber.child) stack.push({ fiber: fiber.child, parent: fiber });
|
|
569
900
|
}
|
|
570
901
|
if (bestHandle && bestScore < (options.minScore ?? 0)) {
|
|
571
902
|
appilotsDebugLog(
|
|
@@ -576,12 +907,14 @@ function findInteractiveByIdentifier(query, options = {}) {
|
|
|
576
907
|
if (bestHandle) {
|
|
577
908
|
const independent = tied.filter(
|
|
578
909
|
(candidate) => !tied.some(
|
|
579
|
-
(other) => other.fiber !== candidate.fiber && isDescendantOf(other.fiber, candidate.fiber)
|
|
910
|
+
(other) => other.fiber !== candidate.fiber && isDescendantOf(other.fiber, candidate.fiber, parents)
|
|
580
911
|
)
|
|
581
912
|
);
|
|
582
913
|
bestHandle.matchCount = independent.length;
|
|
583
914
|
bestHandle.named = bestNamed;
|
|
584
915
|
bestHandle.disabled = bestDisabled;
|
|
916
|
+
const measured = bestFiber;
|
|
917
|
+
bestHandle.measure = () => rectOfFiber(measured);
|
|
585
918
|
const candidates = independent.map(({ fiber, kind }) => describeCandidate(fiber, kind)).filter((id) => typeof id === "string" && id.length > 0).slice(0, 8);
|
|
586
919
|
if (candidates.length > 0) bestHandle.matchCandidates = candidates;
|
|
587
920
|
appilotsDebugLog(
|
|
@@ -593,6 +926,119 @@ function findInteractiveByIdentifier(query, options = {}) {
|
|
|
593
926
|
return bestHandle;
|
|
594
927
|
}
|
|
595
928
|
|
|
929
|
+
// src/introspection/spotlightTarget.ts
|
|
930
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
931
|
+
var current = null;
|
|
932
|
+
var seq = 0;
|
|
933
|
+
var expiry = null;
|
|
934
|
+
var SPOTLIGHT_TTL_MS = 1200;
|
|
935
|
+
var mode = "off";
|
|
936
|
+
function setSpotlightMode(next) {
|
|
937
|
+
mode = next;
|
|
938
|
+
}
|
|
939
|
+
function getSpotlightMode() {
|
|
940
|
+
return mode;
|
|
941
|
+
}
|
|
942
|
+
function emit() {
|
|
943
|
+
for (const listener of listeners) listener(current);
|
|
944
|
+
}
|
|
945
|
+
function setSpotlightTarget(rect, label) {
|
|
946
|
+
if (expiry) {
|
|
947
|
+
clearTimeout(expiry);
|
|
948
|
+
expiry = null;
|
|
949
|
+
}
|
|
950
|
+
if (!rect) {
|
|
951
|
+
appilotsDebugLog(
|
|
952
|
+
`setSpotlightTarget: cleared${label ? ` (was about to mark "${label}")` : ""}`
|
|
953
|
+
);
|
|
954
|
+
if (current === null) return;
|
|
955
|
+
current = null;
|
|
956
|
+
emit();
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
current = { rect, label, seq: ++seq };
|
|
960
|
+
appilotsDebugLog(
|
|
961
|
+
`setSpotlightTarget: seq=${current.seq} label="${label ?? ""}" rect=${Math.round(rect.x)},${Math.round(rect.y)} ${Math.round(rect.width)}x${Math.round(rect.height)}`
|
|
962
|
+
);
|
|
963
|
+
emit();
|
|
964
|
+
expiry = setTimeout(() => {
|
|
965
|
+
expiry = null;
|
|
966
|
+
current = null;
|
|
967
|
+
emit();
|
|
968
|
+
}, SPOTLIGHT_TTL_MS);
|
|
969
|
+
}
|
|
970
|
+
function getSpotlightTarget() {
|
|
971
|
+
return current;
|
|
972
|
+
}
|
|
973
|
+
function subscribeSpotlight(listener) {
|
|
974
|
+
listeners.add(listener);
|
|
975
|
+
return () => {
|
|
976
|
+
listeners.delete(listener);
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// src/introspection/scrollIntoView.ts
|
|
981
|
+
var REVEAL_MARGIN = 24;
|
|
982
|
+
function offsetToReveal({ rect, viewport, metrics }) {
|
|
983
|
+
const maxOffset = Math.max(0, metrics.contentLength - metrics.visibleLength);
|
|
984
|
+
const viewTop = viewport.top + REVEAL_MARGIN;
|
|
985
|
+
const viewBottom = viewport.top + viewport.height - REVEAL_MARGIN;
|
|
986
|
+
const tallerThanViewport = rect.height > viewport.height - REVEAL_MARGIN * 2;
|
|
987
|
+
let delta;
|
|
988
|
+
if (tallerThanViewport) {
|
|
989
|
+
if (rect.y >= viewTop && rect.y <= viewBottom) return null;
|
|
990
|
+
delta = rect.y - viewTop;
|
|
991
|
+
} else if (rect.y < viewTop) {
|
|
992
|
+
delta = rect.y - viewTop;
|
|
993
|
+
} else if (rect.y + rect.height > viewBottom) {
|
|
994
|
+
delta = rect.y + rect.height - viewBottom;
|
|
995
|
+
} else {
|
|
996
|
+
return null;
|
|
997
|
+
}
|
|
998
|
+
const target = clamp(metrics.offset + delta, 0, maxOffset);
|
|
999
|
+
if (Math.abs(target - metrics.offset) < 1) return null;
|
|
1000
|
+
return target;
|
|
1001
|
+
}
|
|
1002
|
+
function clamp(value, min, max) {
|
|
1003
|
+
return Math.min(max, Math.max(min, value));
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// src/introspection/revealTarget.ts
|
|
1007
|
+
function revealTarget(rect) {
|
|
1008
|
+
const host = getGeometryHost();
|
|
1009
|
+
if (!host) return { rect, scrolled: false };
|
|
1010
|
+
if (isRectOnScreen(rect, host.window)) return { rect, scrolled: false };
|
|
1011
|
+
const scrollable = resolveScrollable();
|
|
1012
|
+
if (!scrollable) return { rect, scrolled: false };
|
|
1013
|
+
const metrics = scrollable.getScrollMetrics?.();
|
|
1014
|
+
if (!metrics) return { rect, scrolled: false };
|
|
1015
|
+
const box = scrollable.measureViewport?.();
|
|
1016
|
+
const viewport = box ? { top: box.y, height: box.height } : { top: 0, height: host.window.height };
|
|
1017
|
+
const target = offsetToReveal({ rect, viewport, metrics });
|
|
1018
|
+
if (target === null) return { rect, scrolled: false };
|
|
1019
|
+
const moved = scrollable.scrollToOffset?.(target) ?? false;
|
|
1020
|
+
if (!moved) {
|
|
1021
|
+
appilotsDebugLog(`revealTarget: "${scrollable.id}" refused scrollToOffset(${target})`);
|
|
1022
|
+
return { rect, scrolled: false };
|
|
1023
|
+
}
|
|
1024
|
+
const delta = target - metrics.offset;
|
|
1025
|
+
appilotsDebugLog(
|
|
1026
|
+
`revealTarget: scrolled "${scrollable.id}" by ${Math.round(delta)}px (${Math.round(metrics.offset)} \u2192 ${Math.round(target)})`
|
|
1027
|
+
);
|
|
1028
|
+
return { rect: { ...rect, y: rect.y - delta }, scrolled: true };
|
|
1029
|
+
}
|
|
1030
|
+
function resolveScrollable() {
|
|
1031
|
+
const screen = getCurrentScreen();
|
|
1032
|
+
const candidates = listRegistry.snapshot().filter((entry) => entry.scrollToOffset && entry.getScrollMetrics).filter((entry) => !screen || !entry.screen || entry.screen === screen);
|
|
1033
|
+
if (candidates.length === 1) return candidates[0];
|
|
1034
|
+
if (candidates.length > 1) {
|
|
1035
|
+
appilotsDebugLog(
|
|
1036
|
+
`revealTarget: ${candidates.length} scrollables on "${screen ?? "?"}" \u2014 cannot tell which holds the target, so not scrolling.`
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
return null;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
596
1042
|
// src/executor/handlers/navigateHandler.ts
|
|
597
1043
|
var _reportedMissingNavRef = false;
|
|
598
1044
|
function reportMissingNavigationRef() {
|
|
@@ -635,7 +1081,18 @@ function screenExistsAnywhere(state, screenName) {
|
|
|
635
1081
|
return false;
|
|
636
1082
|
}
|
|
637
1083
|
function safeNavigate(nav, screenName, params, path) {
|
|
1084
|
+
const state = nav.getState?.();
|
|
1085
|
+
const rootState = nav.getRootState?.() ?? state;
|
|
638
1086
|
if (path && path.length > 0) {
|
|
1087
|
+
if (Array.isArray(rootState?.routeNames) && !screenExistsAnywhere(rootState, path[0]) && !state?.routeNames?.includes(path[0])) {
|
|
1088
|
+
if (state?.routeNames?.includes(screenName)) {
|
|
1089
|
+
nav.navigate(screenName, params);
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
throw new Error(
|
|
1093
|
+
`Route "${path[0]}" is not registered in any navigator. Cannot resolve the supplied path to "${screenName}".`
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
639
1096
|
const { root, nestedParams } = buildNestedParams(path, screenName, params);
|
|
640
1097
|
appilotsDebugLog(
|
|
641
1098
|
`safeNavigate: using path-based navigation \u2014 root="${root}", nested=${JSON.stringify(nestedParams)}`
|
|
@@ -643,12 +1100,10 @@ function safeNavigate(nav, screenName, params, path) {
|
|
|
643
1100
|
nav.navigate(root, nestedParams);
|
|
644
1101
|
return;
|
|
645
1102
|
}
|
|
646
|
-
const state = nav.getState?.();
|
|
647
1103
|
if (state?.routeNames?.includes(screenName)) {
|
|
648
1104
|
nav.navigate(screenName, params);
|
|
649
1105
|
return;
|
|
650
1106
|
}
|
|
651
|
-
const rootState = nav.getRootState?.() ?? state;
|
|
652
1107
|
if (!screenExistsAnywhere(rootState, screenName)) {
|
|
653
1108
|
throw new Error(
|
|
654
1109
|
`Screen "${screenName}" is not registered in any navigator. The screen may have been removed, may live inside a flow that is not currently mounted (e.g. Auth stack after login), or the MCP doc is stale.`
|
|
@@ -670,8 +1125,8 @@ function navigateHandler(payload, context) {
|
|
|
670
1125
|
if (!permissions.canNavigate) {
|
|
671
1126
|
return {
|
|
672
1127
|
success: false,
|
|
673
|
-
error: "Navigation
|
|
674
|
-
diagnose: { category: "unknown", screen: payload.screenName }
|
|
1128
|
+
error: permissionDeniedMessage("Navigation", "canNavigate"),
|
|
1129
|
+
diagnose: { category: "unknown", screen: payload.screenName, recoverable: false }
|
|
675
1130
|
};
|
|
676
1131
|
}
|
|
677
1132
|
const isGoBack = payload.navigationAction === "goBack";
|
|
@@ -1028,8 +1483,10 @@ function pressResolvedListItem(list, itemFiber, listIndex, itemIndex) {
|
|
|
1028
1483
|
(candidate) => candidate !== touchable && containsFiber(touchable, candidate)
|
|
1029
1484
|
);
|
|
1030
1485
|
if (primaryWrapsNestedTargets) {
|
|
1486
|
+
const outer = describeTouchable(touchable) ?? "?";
|
|
1487
|
+
const nested = safeIdentifiableTouchables.filter((candidate) => candidate !== touchable && containsFiber(touchable, candidate)).map((candidate) => describeTouchable(candidate) ?? "?").slice(0, 6);
|
|
1031
1488
|
appilotsDebugLog(
|
|
1032
|
-
`listItemDispatch: item "list-${listIndex}-item-${itemIndex}" has nested pressables; pressing outer row target`
|
|
1489
|
+
`listItemDispatch: item "list-${listIndex}-item-${itemIndex}" has nested pressables; pressing outer row target [outer=${outer}] [nested=${nested.join(", ")}] [outerHasOnPress=${typeof touchable?.memoizedProps?.onPress === "function"}]`
|
|
1033
1490
|
);
|
|
1034
1491
|
} else {
|
|
1035
1492
|
const candidates = safeIdentifiableTouchables.map(describeTouchable).filter((d) => !!d).slice(0, 8);
|
|
@@ -1096,10 +1553,12 @@ function scoreItemCandidate(item, zeroBasedIndex, locator) {
|
|
|
1096
1553
|
if (locator.itemKey) {
|
|
1097
1554
|
if (itemKey === locator.itemKey) score += 1e3;
|
|
1098
1555
|
else if (itemKey && normalize2(itemKey) === normalize2(locator.itemKey)) score += 950;
|
|
1556
|
+
else return 0;
|
|
1099
1557
|
}
|
|
1100
1558
|
if (locator.reactKey) {
|
|
1101
1559
|
if (reactKey === locator.reactKey) score += 900;
|
|
1102
1560
|
else if (reactKey && normalize2(reactKey) === normalize2(locator.reactKey)) score += 850;
|
|
1561
|
+
else if (!locator.itemKey) return 0;
|
|
1103
1562
|
}
|
|
1104
1563
|
if (locator.itemIndex && zeroBasedIndex === locator.itemIndex - 1) score += 750;
|
|
1105
1564
|
if (locator.itemIndex && markedIndex !== void 0 && (markedIndex === locator.itemIndex || markedIndex === locator.itemIndex - 1)) {
|
|
@@ -1169,7 +1628,7 @@ function resolveListItemByIdentity(locator) {
|
|
|
1169
1628
|
scoredCandidates.sort((a, b) => b.score - a.score);
|
|
1170
1629
|
const best = scoredCandidates[0];
|
|
1171
1630
|
const tiedCandidates = scoredCandidates.filter((c) => c.score === best.score);
|
|
1172
|
-
if (tiedCandidates.length > 1 && best.score < 750) {
|
|
1631
|
+
if (tiedCandidates.length > 1 && (best.score < 750 || Boolean(effective.itemKey || effective.reactKey))) {
|
|
1173
1632
|
const candidates = tiedCandidates.map((c) => describeItemCandidate(c.listIndex, c.itemIndex - 1, c.item)).slice(0, 8);
|
|
1174
1633
|
return {
|
|
1175
1634
|
ok: false,
|
|
@@ -1237,11 +1696,11 @@ function toggleListItemByIdentity(locator, options) {
|
|
|
1237
1696
|
}
|
|
1238
1697
|
const target = switches[0];
|
|
1239
1698
|
const props = target?.memoizedProps ?? {};
|
|
1240
|
-
const
|
|
1241
|
-
if (options?.expected !== void 0 &&
|
|
1242
|
-
return { ok: true, containerType: list.containerType, value:
|
|
1699
|
+
const current2 = switchValue(props);
|
|
1700
|
+
if (options?.expected !== void 0 && current2 === options.expected) {
|
|
1701
|
+
return { ok: true, containerType: list.containerType, value: current2, noop: true };
|
|
1243
1702
|
}
|
|
1244
|
-
const flipped = flipSwitchFiber(target, !
|
|
1703
|
+
const flipped = flipSwitchFiber(target, !current2);
|
|
1245
1704
|
if (!flipped.ok) {
|
|
1246
1705
|
if (flipped.reason === "no-handler") {
|
|
1247
1706
|
return {
|
|
@@ -1258,7 +1717,7 @@ function toggleListItemByIdentity(locator, options) {
|
|
|
1258
1717
|
containerType: list.containerType
|
|
1259
1718
|
};
|
|
1260
1719
|
}
|
|
1261
|
-
return { ok: true, containerType: list.containerType, value: !
|
|
1720
|
+
return { ok: true, containerType: list.containerType, value: !current2 };
|
|
1262
1721
|
}
|
|
1263
1722
|
function pressListItemByIdentity(locator) {
|
|
1264
1723
|
const resolved = resolveListItemByIdentity(locator);
|
|
@@ -1453,6 +1912,12 @@ function dispatchFiberTarget(targetId, action) {
|
|
|
1453
1912
|
diagnose: screenDiagnose(targetId)
|
|
1454
1913
|
};
|
|
1455
1914
|
}
|
|
1915
|
+
const measured = handle.measure?.() ?? null;
|
|
1916
|
+
if (measured && getSpotlightMode() === "guided") {
|
|
1917
|
+
setSpotlightTarget(revealTarget(measured).rect, handle.label);
|
|
1918
|
+
} else {
|
|
1919
|
+
setSpotlightTarget(measured, handle.label);
|
|
1920
|
+
}
|
|
1456
1921
|
try {
|
|
1457
1922
|
if (isPressLike(action) && handle.kind === "target" && handle.press) {
|
|
1458
1923
|
handle.press();
|
|
@@ -1503,6 +1968,13 @@ function dispatchLegacyTarget(targetId, registry, action) {
|
|
|
1503
1968
|
};
|
|
1504
1969
|
}
|
|
1505
1970
|
function dispatchElement(element, registry, action) {
|
|
1971
|
+
if (element.dispatchable === false) {
|
|
1972
|
+
return {
|
|
1973
|
+
success: false,
|
|
1974
|
+
error: `Element "${element.id}" is observation-only and has no exact live control binding.`,
|
|
1975
|
+
diagnose: { ...screenDiagnose(element.id, "ambiguous-target"), recoverable: false }
|
|
1976
|
+
};
|
|
1977
|
+
}
|
|
1506
1978
|
if (element.disabled === true) {
|
|
1507
1979
|
return {
|
|
1508
1980
|
success: false,
|
|
@@ -1521,6 +1993,13 @@ function dispatchElement(element, registry, action) {
|
|
|
1521
1993
|
texts: element.texts
|
|
1522
1994
|
});
|
|
1523
1995
|
if (pressed.ok) return { success: true };
|
|
1996
|
+
if (!pressed.refusal && (element.listContext.itemKey || element.listContext.reactKey)) {
|
|
1997
|
+
return {
|
|
1998
|
+
success: false,
|
|
1999
|
+
error: pressed.error ?? `The observed list entity "${element.label ?? element.id}" could not be resolved. Reobserve; do not substitute its old position.`,
|
|
2000
|
+
diagnose: { ...screenDiagnose(element.id, "ambiguous-target"), recoverable: true }
|
|
2001
|
+
};
|
|
2002
|
+
}
|
|
1524
2003
|
if (pressed.refusal) {
|
|
1525
2004
|
return {
|
|
1526
2005
|
success: false,
|
|
@@ -1838,12 +2317,18 @@ function normalizeText(value) {
|
|
|
1838
2317
|
function containsWord(haystack, needle) {
|
|
1839
2318
|
return ` ${haystack} `.includes(` ${needle} `);
|
|
1840
2319
|
}
|
|
1841
|
-
function readVisibleFormValidation(snap) {
|
|
2320
|
+
function readVisibleFormValidation(snap, beforeTexts) {
|
|
1842
2321
|
if (!snap) return void 0;
|
|
2322
|
+
const invalid = snap.inputs?.find((input) => input.invalid === true);
|
|
2323
|
+
const hasSubmitControl = snap.buttons?.some(
|
|
2324
|
+
(button) => SUBMIT_CONTROL_RE.test(button.label ?? button.id ?? "")
|
|
2325
|
+
);
|
|
1843
2326
|
for (const text of snap.texts ?? []) {
|
|
1844
|
-
if (!text?.trim() || !
|
|
2327
|
+
if (!text?.trim() || !isVisibleValidationText(text, { invalid: !!invalid, hasSubmitControl, beforeTexts }))
|
|
2328
|
+
continue;
|
|
1845
2329
|
return { fieldId: inferValidationFieldId(text, snap.inputs), message: text };
|
|
1846
2330
|
}
|
|
2331
|
+
if (invalid) return { fieldId: invalid.id, message: "The app marks this field as invalid." };
|
|
1847
2332
|
return void 0;
|
|
1848
2333
|
}
|
|
1849
2334
|
function readVisibleFormSuccess(snap) {
|
|
@@ -1856,15 +2341,18 @@ function submitAsyncStillPending(submitTargetId) {
|
|
|
1856
2341
|
if (submitTargetId && probe.pressedFound && probe.pressedDisabled) return true;
|
|
1857
2342
|
return false;
|
|
1858
2343
|
}
|
|
1859
|
-
function evaluatePostSubmitSnapshot(snap, activePath) {
|
|
1860
|
-
const validation = readVisibleFormValidation(snap);
|
|
2344
|
+
function evaluatePostSubmitSnapshot(snap, activePath, beforeTexts) {
|
|
2345
|
+
const validation = readVisibleFormValidation(snap, beforeTexts);
|
|
1861
2346
|
if (validation) {
|
|
1862
2347
|
return { status: "failure", fieldId: validation.fieldId, message: validation.message };
|
|
1863
2348
|
}
|
|
1864
2349
|
const successText = readVisibleFormSuccess(snap);
|
|
1865
2350
|
if (successText) return { status: "success", message: successText };
|
|
1866
2351
|
if (!snapshotShowsOpenCreateForm(snap, activePath)) {
|
|
1867
|
-
return {
|
|
2352
|
+
return {
|
|
2353
|
+
status: "unverified",
|
|
2354
|
+
message: "The form closed without a visible success receipt. Inspect the resulting record before claiming the goal is complete."
|
|
2355
|
+
};
|
|
1868
2356
|
}
|
|
1869
2357
|
return void 0;
|
|
1870
2358
|
}
|
|
@@ -1880,7 +2368,7 @@ async function waitForPostSubmitOutcome(options) {
|
|
|
1880
2368
|
}
|
|
1881
2369
|
const snap2 = captureSnapshot();
|
|
1882
2370
|
const activePath2 = getActiveRouteNames();
|
|
1883
|
-
const outcome2 = evaluatePostSubmitSnapshot(snap2, activePath2);
|
|
2371
|
+
const outcome2 = evaluatePostSubmitSnapshot(snap2, activePath2, options?.beforeTexts);
|
|
1884
2372
|
if (outcome2) return outcome2;
|
|
1885
2373
|
await sleep(pollMs);
|
|
1886
2374
|
}
|
|
@@ -1892,7 +2380,7 @@ async function waitForPostSubmitOutcome(options) {
|
|
|
1892
2380
|
}
|
|
1893
2381
|
const snap = captureSnapshot();
|
|
1894
2382
|
const activePath = getActiveRouteNames();
|
|
1895
|
-
const outcome = evaluatePostSubmitSnapshot(snap, activePath);
|
|
2383
|
+
const outcome = evaluatePostSubmitSnapshot(snap, activePath, options?.beforeTexts);
|
|
1896
2384
|
if (outcome) return outcome;
|
|
1897
2385
|
return {
|
|
1898
2386
|
status: "failure",
|
|
@@ -1900,91 +2388,6 @@ async function waitForPostSubmitOutcome(options) {
|
|
|
1900
2388
|
};
|
|
1901
2389
|
}
|
|
1902
2390
|
|
|
1903
|
-
// ../shared/dist/chunk-ERITSWKE.mjs
|
|
1904
|
-
var DEFAULT_FUZZY_FLOOR = 4;
|
|
1905
|
-
function normalizeId(value) {
|
|
1906
|
-
return String(value ?? "").normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1907
|
-
}
|
|
1908
|
-
var KIND_PREFIX_WORDS = "button|switch|toggle|input|field|link|btn|slider";
|
|
1909
|
-
var KEBAB_SNAKE_PREFIX_RE = new RegExp(`^(?:${KIND_PREFIX_WORDS})[-_]`, "i");
|
|
1910
|
-
var CAMEL_PREFIX_RE = new RegExp(`^(?:${KIND_PREFIX_WORDS})(?=[A-Z])`, "");
|
|
1911
|
-
function stripKindPrefix(id) {
|
|
1912
|
-
const raw = String(id ?? "");
|
|
1913
|
-
if (KEBAB_SNAKE_PREFIX_RE.test(raw)) return raw.replace(KEBAB_SNAKE_PREFIX_RE, "");
|
|
1914
|
-
if (CAMEL_PREFIX_RE.test(raw)) return raw.replace(CAMEL_PREFIX_RE, "");
|
|
1915
|
-
return raw;
|
|
1916
|
-
}
|
|
1917
|
-
function actionAllowsKind(action, kind) {
|
|
1918
|
-
if (!action || !kind) return true;
|
|
1919
|
-
switch (action) {
|
|
1920
|
-
case "press":
|
|
1921
|
-
case "longPress":
|
|
1922
|
-
return kind !== "field" && kind !== "input" && kind !== "slider";
|
|
1923
|
-
case "set_value":
|
|
1924
|
-
return kind === "slider" || kind === "field" || kind === "input";
|
|
1925
|
-
case "focus":
|
|
1926
|
-
return kind === "field" || kind === "input";
|
|
1927
|
-
case "toggle":
|
|
1928
|
-
return kind === "toggle";
|
|
1929
|
-
default:
|
|
1930
|
-
return true;
|
|
1931
|
-
}
|
|
1932
|
-
}
|
|
1933
|
-
function distinctIds(candidates) {
|
|
1934
|
-
return [...new Set(candidates.map((c) => c.id))];
|
|
1935
|
-
}
|
|
1936
|
-
function resolveTarget(requested, candidates, opts = {}) {
|
|
1937
|
-
const floor = opts.fuzzyFloor ?? DEFAULT_FUZZY_FLOOR;
|
|
1938
|
-
const nReq = normalizeId(requested);
|
|
1939
|
-
if (!nReq || candidates.length === 0) {
|
|
1940
|
-
return { status: "unverified", reason: "not-found" };
|
|
1941
|
-
}
|
|
1942
|
-
for (const c of candidates) {
|
|
1943
|
-
if (normalizeId(c.id) === nReq) {
|
|
1944
|
-
return { status: "matched", id: c.id, tier: "exact" };
|
|
1945
|
-
}
|
|
1946
|
-
}
|
|
1947
|
-
const labelExact = distinctIds(
|
|
1948
|
-
candidates.filter((c) => c.label && normalizeId(c.label) === nReq)
|
|
1949
|
-
);
|
|
1950
|
-
if (labelExact.length === 1) return { status: "matched", id: labelExact[0], tier: "exact" };
|
|
1951
|
-
if (labelExact.length > 1) return { status: "unverified", reason: "ambiguous" };
|
|
1952
|
-
const nReqStripped = normalizeId(stripKindPrefix(requested));
|
|
1953
|
-
for (const c of candidates) {
|
|
1954
|
-
const nId = normalizeId(c.id);
|
|
1955
|
-
const nIdStripped = normalizeId(stripKindPrefix(c.id));
|
|
1956
|
-
if (nReqStripped && (nReqStripped === nId || nReqStripped === nIdStripped) || nIdStripped && nIdStripped === nReq) {
|
|
1957
|
-
return { status: "matched", id: c.id, tier: "prefix" };
|
|
1958
|
-
}
|
|
1959
|
-
}
|
|
1960
|
-
if (opts.destructive) {
|
|
1961
|
-
return { status: "unverified", reason: "destructive-inexact" };
|
|
1962
|
-
}
|
|
1963
|
-
if (nReq.length < floor) {
|
|
1964
|
-
return { status: "unverified", reason: "not-found" };
|
|
1965
|
-
}
|
|
1966
|
-
const fuzzy = candidates.filter((c) => {
|
|
1967
|
-
for (const s of [c.id, c.label]) {
|
|
1968
|
-
if (!s) continue;
|
|
1969
|
-
const ns = normalizeId(s);
|
|
1970
|
-
if (ns.length >= floor && (ns.includes(nReq) || nReq.includes(ns))) return true;
|
|
1971
|
-
}
|
|
1972
|
-
return false;
|
|
1973
|
-
});
|
|
1974
|
-
if (fuzzy.length === 0) {
|
|
1975
|
-
return { status: "unverified", reason: "not-found" };
|
|
1976
|
-
}
|
|
1977
|
-
const typed = fuzzy.filter((c) => actionAllowsKind(opts.action, c.kind));
|
|
1978
|
-
if (typed.length === 0) {
|
|
1979
|
-
return { status: "unverified", reason: "type-mismatch" };
|
|
1980
|
-
}
|
|
1981
|
-
const ids = distinctIds(typed);
|
|
1982
|
-
if (ids.length > 1) {
|
|
1983
|
-
return { status: "unverified", reason: "ambiguous" };
|
|
1984
|
-
}
|
|
1985
|
-
return { status: "matched", id: ids[0], tier: "fuzzy" };
|
|
1986
|
-
}
|
|
1987
|
-
|
|
1988
2391
|
// src/executor/handlers/uiInteractionHandler.ts
|
|
1989
2392
|
function findSnapshotButton(targetId) {
|
|
1990
2393
|
try {
|
|
@@ -2057,6 +2460,7 @@ function stripComponentPrefix(componentId) {
|
|
|
2057
2460
|
return null;
|
|
2058
2461
|
}
|
|
2059
2462
|
function resolveComponentId(componentId, registry, action) {
|
|
2463
|
+
if (isSourceControlId(componentId)) return componentId;
|
|
2060
2464
|
if (registry.getTarget(componentId) || registry.getToggle(componentId) || registry.getField(componentId) || registry.getSlider(componentId)) {
|
|
2061
2465
|
return componentId;
|
|
2062
2466
|
}
|
|
@@ -2070,6 +2474,7 @@ function resolveComponentId(componentId, registry, action) {
|
|
|
2070
2474
|
return comp.id;
|
|
2071
2475
|
}
|
|
2072
2476
|
}
|
|
2477
|
+
if (isPositionalControlId(componentId)) return componentId;
|
|
2073
2478
|
const currentScreen = getCurrentScreen();
|
|
2074
2479
|
if (currentScreen) {
|
|
2075
2480
|
const screenMeta = getScreenMetadata(currentScreen);
|
|
@@ -2315,11 +2720,14 @@ function uiInteractionHandler(payload, context) {
|
|
|
2315
2720
|
if (!permissions.canInteractUI) {
|
|
2316
2721
|
return {
|
|
2317
2722
|
success: false,
|
|
2318
|
-
error: "UI interaction
|
|
2319
|
-
diagnose:
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2723
|
+
error: permissionDeniedMessage("UI interaction", "canInteractUI"),
|
|
2724
|
+
diagnose: {
|
|
2725
|
+
...buildUiDiagnose(
|
|
2726
|
+
payload.componentId ?? payload.targetId ?? "",
|
|
2727
|
+
"unknown"
|
|
2728
|
+
),
|
|
2729
|
+
recoverable: false
|
|
2730
|
+
}
|
|
2323
2731
|
};
|
|
2324
2732
|
}
|
|
2325
2733
|
const { action } = payload;
|
|
@@ -2549,6 +2957,28 @@ function uiInteractionHandler(payload, context) {
|
|
|
2549
2957
|
};
|
|
2550
2958
|
}
|
|
2551
2959
|
|
|
2960
|
+
// src/executor/completeTextEdit.ts
|
|
2961
|
+
var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2962
|
+
async function completeTextEdit(fieldId, expected, registered, origin, resolveRegistered) {
|
|
2963
|
+
await sleep2(80);
|
|
2964
|
+
const current2 = findInteractiveByIdentifier(fieldId, { kind: "field" });
|
|
2965
|
+
if (origin?.controlIdentity && current2?.controlIdentity !== origin.controlIdentity) return false;
|
|
2966
|
+
if (registered && resolveRegistered && resolveRegistered() !== registered) return false;
|
|
2967
|
+
if (registered?.finishEditing) registered.finishEditing();
|
|
2968
|
+
else if (current2 && (current2.matchCount ?? 1) === 1 && !current2.disabled)
|
|
2969
|
+
current2.finishEditing?.();
|
|
2970
|
+
const started = Date.now();
|
|
2971
|
+
do {
|
|
2972
|
+
await sleep2(80);
|
|
2973
|
+
const live = findInteractiveByIdentifier(fieldId, { kind: "field" });
|
|
2974
|
+
if (origin?.controlIdentity && live?.controlIdentity !== origin.controlIdentity) return false;
|
|
2975
|
+
if (registered && resolveRegistered && resolveRegistered() !== registered) return false;
|
|
2976
|
+
const value = registered ? registered.getValue() : live?.getValue?.();
|
|
2977
|
+
if (value !== void 0 && value !== null && String(value) === String(expected)) return true;
|
|
2978
|
+
} while (Date.now() - started < 480);
|
|
2979
|
+
return false;
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2552
2982
|
// src/executor/formSubmitResolve.ts
|
|
2553
2983
|
var SUBMIT_LABEL_HINT_RE = /\b(submit|save|send|continue|next|sign\s*in|log\s*in|register|salvar|enviar|entrar|cadastrar|continuar|proximo|próximo|avancar|avançar|confirmar|guardar|registrarse|iniciar\s+sesi[óo]n|enregistrer|envoyer|valider|soumettre|confirmer|se\s+connecter|s['e]\s*inscrire)\b/i;
|
|
2554
2984
|
var AGENT_CHROME_TARGET_RE = /\b(fechar|close|dismiss|cancelar|cancel|limpar|clear|chat\s+do\s+assistente|assistant\s+chat)\b/i;
|
|
@@ -2695,8 +3125,8 @@ function selectValueApplied(fieldId, requestedValue, currentValue, registry) {
|
|
|
2695
3125
|
function getMetadataField(fieldId, registry) {
|
|
2696
3126
|
const screens = /* @__PURE__ */ new Set();
|
|
2697
3127
|
for (const name of getActiveRouteNames()) screens.add(name);
|
|
2698
|
-
const
|
|
2699
|
-
if (
|
|
3128
|
+
const current2 = getCurrentScreen();
|
|
3129
|
+
if (current2) screens.add(current2);
|
|
2700
3130
|
if (registry) {
|
|
2701
3131
|
for (const comp of registry.snapshot()) {
|
|
2702
3132
|
if (comp.kind === "field" && comp.screen) screens.add(comp.screen);
|
|
@@ -2737,7 +3167,7 @@ async function tryOpenSelectModal(fieldId, registry) {
|
|
|
2737
3167
|
if (!target) continue;
|
|
2738
3168
|
try {
|
|
2739
3169
|
target.press();
|
|
2740
|
-
await
|
|
3170
|
+
await sleep3(180);
|
|
2741
3171
|
return true;
|
|
2742
3172
|
} catch {
|
|
2743
3173
|
}
|
|
@@ -3122,7 +3552,7 @@ function findSlider(fieldId, registry) {
|
|
|
3122
3552
|
}
|
|
3123
3553
|
return void 0;
|
|
3124
3554
|
}
|
|
3125
|
-
var
|
|
3555
|
+
var sleep3 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
3126
3556
|
async function formFillHandler(payload, context) {
|
|
3127
3557
|
const { permissions } = context;
|
|
3128
3558
|
const registry = context.registry ?? componentRegistry;
|
|
@@ -3131,8 +3561,8 @@ async function formFillHandler(payload, context) {
|
|
|
3131
3561
|
if (!permissions.canFillForms) {
|
|
3132
3562
|
return {
|
|
3133
3563
|
success: false,
|
|
3134
|
-
error: "Form filling
|
|
3135
|
-
diagnose: { category: "unknown", screen: screenPath }
|
|
3564
|
+
error: permissionDeniedMessage("Form filling", "canFillForms"),
|
|
3565
|
+
diagnose: { category: "unknown", screen: screenPath, recoverable: false }
|
|
3136
3566
|
};
|
|
3137
3567
|
}
|
|
3138
3568
|
if (!payload.fields || payload.fields.length === 0) {
|
|
@@ -3189,14 +3619,51 @@ async function formFillHandler(payload, context) {
|
|
|
3189
3619
|
const fieldEntry = findField(fieldId, registry);
|
|
3190
3620
|
if (fieldEntry) {
|
|
3191
3621
|
try {
|
|
3622
|
+
const nativeField = findInteractiveByIdentifier(fieldId, { kind: "field" });
|
|
3623
|
+
if (nativeField?.disabled || (nativeField?.matchCount ?? 1) > 1) {
|
|
3624
|
+
fieldResults.push({
|
|
3625
|
+
fieldId,
|
|
3626
|
+
success: false,
|
|
3627
|
+
error: nativeField?.disabled ? "The field is disabled" : "The field identifier matches multiple controls",
|
|
3628
|
+
diagnose: {
|
|
3629
|
+
category: nativeField?.disabled ? "disabled" : "ambiguous-target",
|
|
3630
|
+
fieldId,
|
|
3631
|
+
screen: screenPath
|
|
3632
|
+
}
|
|
3633
|
+
});
|
|
3634
|
+
allSuccess = false;
|
|
3635
|
+
continue;
|
|
3636
|
+
}
|
|
3192
3637
|
appilotsDebugLog(
|
|
3193
3638
|
`formFillHandler: found field "${fieldId}" via registry, calling setValue(<${String(value).length} chars>)`
|
|
3194
3639
|
);
|
|
3195
3640
|
const beforeValue = effectiveFieldType === "date" ? fieldEntry.getValue?.() ?? "" : void 0;
|
|
3196
3641
|
fieldEntry.focus?.();
|
|
3197
3642
|
fieldEntry.setValue(String(value));
|
|
3643
|
+
if (effectiveFieldType === "text" || effectiveFieldType === "number") {
|
|
3644
|
+
if (!await completeTextEdit(
|
|
3645
|
+
fieldId,
|
|
3646
|
+
value,
|
|
3647
|
+
fieldEntry,
|
|
3648
|
+
nativeField,
|
|
3649
|
+
() => findField(fieldId, registry)
|
|
3650
|
+
)) {
|
|
3651
|
+
fieldResults.push({
|
|
3652
|
+
fieldId,
|
|
3653
|
+
success: false,
|
|
3654
|
+
error: "The field did not retain the requested value after editing ended. Inspect its current value before retrying.",
|
|
3655
|
+
diagnose: { category: "validation", fieldId, screen: screenPath }
|
|
3656
|
+
});
|
|
3657
|
+
allSuccess = false;
|
|
3658
|
+
continue;
|
|
3659
|
+
}
|
|
3660
|
+
}
|
|
3198
3661
|
if (effectiveFieldType === "date") {
|
|
3199
|
-
|
|
3662
|
+
let afterValue = fieldEntry.getValue?.() ?? "";
|
|
3663
|
+
if (afterValue === beforeValue) {
|
|
3664
|
+
await sleep3(80);
|
|
3665
|
+
afterValue = fieldEntry.getValue?.() ?? "";
|
|
3666
|
+
}
|
|
3200
3667
|
if (afterValue === beforeValue) {
|
|
3201
3668
|
fieldResults.push({
|
|
3202
3669
|
fieldId,
|
|
@@ -3216,7 +3683,7 @@ async function formFillHandler(payload, context) {
|
|
|
3216
3683
|
if (effectiveFieldType === "select") {
|
|
3217
3684
|
let after = fieldEntry.getValue?.() ?? "";
|
|
3218
3685
|
if (!selectValueApplied(fieldId, value, after, registry)) {
|
|
3219
|
-
await
|
|
3686
|
+
await sleep3(80);
|
|
3220
3687
|
after = fieldEntry.getValue?.() ?? "";
|
|
3221
3688
|
}
|
|
3222
3689
|
if (!selectValueApplied(fieldId, value, after, registry)) {
|
|
@@ -3337,7 +3804,7 @@ async function formFillHandler(payload, context) {
|
|
|
3337
3804
|
if (!selectResult.success && selectedChoiceThisBatch) {
|
|
3338
3805
|
const nextResult = pressContinuationButton(registry);
|
|
3339
3806
|
if (nextResult.success) {
|
|
3340
|
-
await
|
|
3807
|
+
await sleep3(180);
|
|
3341
3808
|
selectResult = await selectVisibleChoice(fieldId, value, registry, {
|
|
3342
3809
|
allowFirstVisibleWhenNoCue: true
|
|
3343
3810
|
});
|
|
@@ -3348,7 +3815,7 @@ async function formFillHandler(payload, context) {
|
|
|
3348
3815
|
appilotsDebugLog(
|
|
3349
3816
|
`formFillHandler: selected visible option for "${fieldId}"` + (selectResult.selectedLabel ? ` \u2192 "${selectResult.selectedLabel}"` : "")
|
|
3350
3817
|
);
|
|
3351
|
-
await
|
|
3818
|
+
await sleep3(120);
|
|
3352
3819
|
fieldResults.push({ fieldId, success: true });
|
|
3353
3820
|
continue;
|
|
3354
3821
|
}
|
|
@@ -3360,8 +3827,34 @@ async function formFillHandler(payload, context) {
|
|
|
3360
3827
|
});
|
|
3361
3828
|
if (handle?.setValue) {
|
|
3362
3829
|
try {
|
|
3830
|
+
if (handle.disabled || (handle.matchCount ?? 1) > 1) {
|
|
3831
|
+
fieldResults.push({
|
|
3832
|
+
fieldId,
|
|
3833
|
+
success: false,
|
|
3834
|
+
error: handle.disabled ? "The field is disabled" : "The field identifier matches multiple controls",
|
|
3835
|
+
diagnose: {
|
|
3836
|
+
category: handle.disabled ? "disabled" : "ambiguous-target",
|
|
3837
|
+
fieldId,
|
|
3838
|
+
screen: screenPath,
|
|
3839
|
+
candidates: handle.matchCandidates
|
|
3840
|
+
}
|
|
3841
|
+
});
|
|
3842
|
+
allSuccess = false;
|
|
3843
|
+
continue;
|
|
3844
|
+
}
|
|
3845
|
+
setSpotlightTarget(handle.measure?.() ?? null, handle.label);
|
|
3363
3846
|
handle.focus?.();
|
|
3364
3847
|
handle.setValue(value);
|
|
3848
|
+
if (handle.kind === "field" && !await completeTextEdit(fieldId, value, void 0, handle)) {
|
|
3849
|
+
fieldResults.push({
|
|
3850
|
+
fieldId,
|
|
3851
|
+
success: false,
|
|
3852
|
+
error: "The field did not retain the requested value after editing ended. Inspect its current value before retrying.",
|
|
3853
|
+
diagnose: { category: "validation", fieldId, screen: screenPath }
|
|
3854
|
+
});
|
|
3855
|
+
allSuccess = false;
|
|
3856
|
+
continue;
|
|
3857
|
+
}
|
|
3365
3858
|
appilotsDebugLog(
|
|
3366
3859
|
`formFillHandler: fiber fallback succeeded for "${fieldId}" \u2192 label="${handle.label}"`
|
|
3367
3860
|
);
|
|
@@ -3399,13 +3892,25 @@ async function formFillHandler(payload, context) {
|
|
|
3399
3892
|
allSuccess = false;
|
|
3400
3893
|
}
|
|
3401
3894
|
if (payload.submitAfterFill === true) {
|
|
3895
|
+
if (!permissions.canSubmitForms) {
|
|
3896
|
+
return {
|
|
3897
|
+
success: false,
|
|
3898
|
+
error: "Form submission is not permitted",
|
|
3899
|
+
fieldResults,
|
|
3900
|
+
diagnose: { category: "unknown", screen: screenPath }
|
|
3901
|
+
};
|
|
3902
|
+
}
|
|
3402
3903
|
if (!allSuccess) {
|
|
3403
3904
|
const failedIds = fieldResults.filter((result) => !result.success).map((result) => result.fieldId);
|
|
3404
3905
|
appilotsDebugWarn(
|
|
3405
3906
|
"formFillHandler: submitAfterFill=true requested but some fields failed; NOT submitting" + (failedIds.length > 0 ? ` (failed: ${failedIds.join(", ")})` : "")
|
|
3406
3907
|
);
|
|
3407
3908
|
} else {
|
|
3408
|
-
await
|
|
3909
|
+
await sleep3(120);
|
|
3910
|
+
if (!permissions.canSubmitForms) {
|
|
3911
|
+
return { success: false, error: "Form submission is not permitted", fieldResults };
|
|
3912
|
+
}
|
|
3913
|
+
const beforeSubmitTexts = captureSnapshot().texts;
|
|
3409
3914
|
const submitResult = pressSubmitButton(
|
|
3410
3915
|
{
|
|
3411
3916
|
screen,
|
|
@@ -3425,7 +3930,8 @@ async function formFillHandler(payload, context) {
|
|
|
3425
3930
|
};
|
|
3426
3931
|
}
|
|
3427
3932
|
const postSubmit = await waitForPostSubmitOutcome({
|
|
3428
|
-
submitTargetId: submitResult.pressedTargetId
|
|
3933
|
+
submitTargetId: submitResult.pressedTargetId,
|
|
3934
|
+
beforeTexts: beforeSubmitTexts
|
|
3429
3935
|
});
|
|
3430
3936
|
if (postSubmit.status === "pending") {
|
|
3431
3937
|
appilotsDebugWarn(`formFillHandler: submit still pending \u2014 ${postSubmit.message}`);
|
|
@@ -3452,6 +3958,9 @@ async function formFillHandler(payload, context) {
|
|
|
3452
3958
|
}
|
|
3453
3959
|
};
|
|
3454
3960
|
}
|
|
3961
|
+
if (postSubmit.status === "unverified") {
|
|
3962
|
+
return { success: true, fieldResults, effect: "unknown" };
|
|
3963
|
+
}
|
|
3455
3964
|
appilotsDebugLog("formFillHandler: submitAfterFill \u2014 submit completed");
|
|
3456
3965
|
}
|
|
3457
3966
|
}
|
|
@@ -3589,7 +4098,21 @@ function aggregateFieldDiagnoses(results, screen) {
|
|
|
3589
4098
|
var FALLBACK_PAGE_PX = 600;
|
|
3590
4099
|
var PAGE_FRACTION = 0.85;
|
|
3591
4100
|
var POST_SCROLL_MOUNT_MS = 400;
|
|
3592
|
-
var
|
|
4101
|
+
var PAGINATION_MAX_WAIT_MS = 4e3;
|
|
4102
|
+
var PAGINATION_POLL_MS = 150;
|
|
4103
|
+
var sleep4 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
4104
|
+
async function settleAfterScroll() {
|
|
4105
|
+
await sleep4(POST_SCROLL_MOUNT_MS);
|
|
4106
|
+
if (!probeLoadingState(void 0, getCurrentScreen()).loading) return;
|
|
4107
|
+
const deadline = Date.now() + PAGINATION_MAX_WAIT_MS;
|
|
4108
|
+
while (Date.now() < deadline) {
|
|
4109
|
+
await sleep4(PAGINATION_POLL_MS);
|
|
4110
|
+
if (!probeLoadingState(void 0, getCurrentScreen()).loading) {
|
|
4111
|
+
await sleep4(POST_SCROLL_MOUNT_MS);
|
|
4112
|
+
return;
|
|
4113
|
+
}
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
3593
4116
|
function normalizeId2(value) {
|
|
3594
4117
|
return String(value ?? "").toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "");
|
|
3595
4118
|
}
|
|
@@ -3625,13 +4148,26 @@ function pageOffset(metrics, direction) {
|
|
|
3625
4148
|
}
|
|
3626
4149
|
return next;
|
|
3627
4150
|
}
|
|
4151
|
+
async function finishScroll(entry, before) {
|
|
4152
|
+
await settleAfterScroll();
|
|
4153
|
+
const current2 = listRegistry.get(entry.id) ?? entry;
|
|
4154
|
+
const after = current2.getScrollMetrics?.();
|
|
4155
|
+
const grew = typeof entry.itemCount === "number" && typeof current2.itemCount === "number" && current2.itemCount > entry.itemCount;
|
|
4156
|
+
const moved = grew || !!(before && after && (Math.abs(after.offset - before.offset) > 2 || after.contentLength > before.contentLength + 2));
|
|
4157
|
+
const outcome = moved ? "moved" : before && after ? "no-progress" : "unverified";
|
|
4158
|
+
current2.exploration?.recordScroll(outcome);
|
|
4159
|
+
return {
|
|
4160
|
+
success: true,
|
|
4161
|
+
effect: moved ? "changed" : outcome === "no-progress" ? "none" : "unknown"
|
|
4162
|
+
};
|
|
4163
|
+
}
|
|
3628
4164
|
async function scrollListHandler(payload, context) {
|
|
3629
4165
|
const { permissions } = context;
|
|
3630
4166
|
if (!permissions.canInteractUI) {
|
|
3631
4167
|
return {
|
|
3632
4168
|
success: false,
|
|
3633
|
-
error: "UI interaction
|
|
3634
|
-
diagnose: buildDiagnose("unknown", payload.listId)
|
|
4169
|
+
error: permissionDeniedMessage("UI interaction", "canInteractUI"),
|
|
4170
|
+
diagnose: { ...buildDiagnose("unknown", payload.listId), recoverable: false }
|
|
3635
4171
|
};
|
|
3636
4172
|
}
|
|
3637
4173
|
const toIndex = typeof payload.toIndex === "number" ? payload.toIndex : void 0;
|
|
@@ -3659,6 +4195,15 @@ async function scrollListHandler(payload, context) {
|
|
|
3659
4195
|
diagnose: buildDiagnose("component-not-found", payload.listId, candidates)
|
|
3660
4196
|
};
|
|
3661
4197
|
}
|
|
4198
|
+
const exploration = entry.exploration?.snapshot();
|
|
4199
|
+
if (exploration && (exploration.remainingScrollSteps === 0 || exploration.consecutiveNoProgress >= 2)) {
|
|
4200
|
+
return {
|
|
4201
|
+
success: false,
|
|
4202
|
+
effect: "none",
|
|
4203
|
+
error: "List exploration stopped: scroll budget exhausted or repeated no progress. Use the current loaded count/coverage and report any remaining uncertainty; do not repeat this scroll.",
|
|
4204
|
+
diagnose: { ...buildDiagnose("validation", entry.id), recoverable: false }
|
|
4205
|
+
};
|
|
4206
|
+
}
|
|
3662
4207
|
if (toIndex !== void 0) {
|
|
3663
4208
|
if (entry.kind === "scroll") {
|
|
3664
4209
|
return {
|
|
@@ -3674,17 +4219,16 @@ async function scrollListHandler(payload, context) {
|
|
|
3674
4219
|
diagnose: buildDiagnose("validation", entry.id)
|
|
3675
4220
|
};
|
|
3676
4221
|
}
|
|
4222
|
+
const before = entry.getScrollMetrics?.();
|
|
3677
4223
|
const zeroBased = toIndex - 1;
|
|
3678
4224
|
if (entry.scrollToIndex?.(zeroBased)) {
|
|
3679
|
-
|
|
3680
|
-
return { success: true };
|
|
4225
|
+
return finishScroll(entry, before);
|
|
3681
4226
|
}
|
|
3682
4227
|
const metrics2 = entry.getScrollMetrics?.();
|
|
3683
4228
|
if (metrics2 && metrics2.contentLength > 0 && typeof entry.itemCount === "number" && entry.itemCount > 0) {
|
|
3684
4229
|
const estimated = zeroBased / entry.itemCount * metrics2.contentLength;
|
|
3685
4230
|
if (entry.scrollToOffset?.(Math.max(0, estimated))) {
|
|
3686
|
-
|
|
3687
|
-
return { success: true };
|
|
4231
|
+
return finishScroll(entry, before);
|
|
3688
4232
|
}
|
|
3689
4233
|
}
|
|
3690
4234
|
return {
|
|
@@ -3694,10 +4238,21 @@ async function scrollListHandler(payload, context) {
|
|
|
3694
4238
|
};
|
|
3695
4239
|
}
|
|
3696
4240
|
const metrics = entry.getScrollMetrics?.();
|
|
4241
|
+
if (!metrics && exploration?.lastScroll === "unverified") {
|
|
4242
|
+
return {
|
|
4243
|
+
success: false,
|
|
4244
|
+
effect: "unknown",
|
|
4245
|
+
error: "Scroll offset is still unavailable after one probe. Cannot safely page again; do not infer the end or repeat the same absolute offset.",
|
|
4246
|
+
diagnose: { ...buildDiagnose("validation", entry.id), recoverable: false }
|
|
4247
|
+
};
|
|
4248
|
+
}
|
|
3697
4249
|
const target = pageOffset(metrics, direction);
|
|
4250
|
+
if (metrics && Math.abs(target - metrics.offset) <= 2) {
|
|
4251
|
+
entry.exploration?.recordScroll("boundary");
|
|
4252
|
+
return { success: true, effect: "none" };
|
|
4253
|
+
}
|
|
3698
4254
|
if (entry.scrollToOffset?.(target)) {
|
|
3699
|
-
|
|
3700
|
-
return { success: true };
|
|
4255
|
+
return finishScroll(entry, metrics);
|
|
3701
4256
|
}
|
|
3702
4257
|
return {
|
|
3703
4258
|
success: false,
|
|
@@ -3711,6 +4266,59 @@ async function confirmHandler(_actionId, _payload) {
|
|
|
3711
4266
|
return { success: true, effect: "unknown" };
|
|
3712
4267
|
}
|
|
3713
4268
|
|
|
4269
|
+
// src/platform/asyncBudget.ts
|
|
4270
|
+
var POST_ACTION_LOADING_MAX_MS = 6e3;
|
|
4271
|
+
var INFERRED_ASYNC_LOADING_MAX_MS = 1e4;
|
|
4272
|
+
var POST_NAVIGATE_LOADING_MAX_MS = 3e3;
|
|
4273
|
+
var MAX_DECLARED_ASYNC_BUDGET_MS = 12e4;
|
|
4274
|
+
var ASYNC_BUSY_CEILING_MS = MAX_DECLARED_ASYNC_BUDGET_MS;
|
|
4275
|
+
function declaredBudgetMs(hints) {
|
|
4276
|
+
const declared = hints?.asyncBudgetMs;
|
|
4277
|
+
if (typeof declared !== "number" || !Number.isFinite(declared) || declared <= 0) return void 0;
|
|
4278
|
+
return Math.min(declared, MAX_DECLARED_ASYNC_BUDGET_MS);
|
|
4279
|
+
}
|
|
4280
|
+
function resolveSettleCapMs(hints) {
|
|
4281
|
+
const declared = declaredBudgetMs(hints);
|
|
4282
|
+
if (declared !== void 0) return declared;
|
|
4283
|
+
return hints?.isAsyncTrigger === true ? INFERRED_ASYNC_LOADING_MAX_MS : POST_ACTION_LOADING_MAX_MS;
|
|
4284
|
+
}
|
|
4285
|
+
function resolveBusyCeilingMs(hints) {
|
|
4286
|
+
return declaredBudgetMs(hints) === void 0 ? ASYNC_BUSY_CEILING_MS : void 0;
|
|
4287
|
+
}
|
|
4288
|
+
function resolvePostNavigateCapMs(hints) {
|
|
4289
|
+
return Math.max(POST_NAVIGATE_LOADING_MAX_MS, declaredBudgetMs(hints) ?? 0);
|
|
4290
|
+
}
|
|
4291
|
+
function resolveActionHints(screen, pressedComponentId, actionPayload) {
|
|
4292
|
+
if (actionPayload && typeof actionPayload === "object") {
|
|
4293
|
+
const payload = actionPayload;
|
|
4294
|
+
const inline = payload._appilotsInferred;
|
|
4295
|
+
const declared = payload._appilotsAsyncBudgetMs;
|
|
4296
|
+
if (inline && typeof inline === "object") {
|
|
4297
|
+
const hints = { ...inline };
|
|
4298
|
+
if (typeof declared === "number") hints.asyncBudgetMs = declared;
|
|
4299
|
+
return hints;
|
|
4300
|
+
}
|
|
4301
|
+
if (typeof declared === "number") return { asyncBudgetMs: declared };
|
|
4302
|
+
}
|
|
4303
|
+
if (!screen || !pressedComponentId) return void 0;
|
|
4304
|
+
const meta = getScreenMetadata(screen);
|
|
4305
|
+
const actions = meta?.actions;
|
|
4306
|
+
if (!actions || actions.length === 0) return void 0;
|
|
4307
|
+
for (const a of actions) {
|
|
4308
|
+
if (typeof a === "string") continue;
|
|
4309
|
+
if (idLooselyMatches(a.id, pressedComponentId)) {
|
|
4310
|
+
const inferred = a.appilotsInferred;
|
|
4311
|
+
const declared = a.asyncBudgetMs;
|
|
4312
|
+
const hints = {
|
|
4313
|
+
...inferred && typeof inferred === "object" ? inferred : {},
|
|
4314
|
+
...typeof declared === "number" ? { asyncBudgetMs: declared } : {}
|
|
4315
|
+
};
|
|
4316
|
+
return Object.keys(hints).length > 0 ? hints : void 0;
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4319
|
+
return void 0;
|
|
4320
|
+
}
|
|
4321
|
+
|
|
3714
4322
|
// src/executor/postInteractionSettle.ts
|
|
3715
4323
|
var sleepMs = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
3716
4324
|
function targetIdFromPayload(payload) {
|
|
@@ -3728,7 +4336,7 @@ function hashFieldValue(value) {
|
|
|
3728
4336
|
}
|
|
3729
4337
|
return (hash >>> 0).toString(36);
|
|
3730
4338
|
}
|
|
3731
|
-
var DISCRETE_FIELD_TYPES = /* @__PURE__ */ new Set(["select", "date"]);
|
|
4339
|
+
var DISCRETE_FIELD_TYPES = /* @__PURE__ */ new Set(["select", "date", "number"]);
|
|
3732
4340
|
function discreteFieldSignature() {
|
|
3733
4341
|
const sigs = [];
|
|
3734
4342
|
for (const { id, kind } of componentRegistry.snapshot()) {
|
|
@@ -3742,12 +4350,23 @@ function discreteFieldSignature() {
|
|
|
3742
4350
|
}
|
|
3743
4351
|
return sigs.sort().join(",");
|
|
3744
4352
|
}
|
|
3745
|
-
|
|
4353
|
+
function capturePreInteractionBaseline(payload) {
|
|
4354
|
+
const targetId = targetIdFromPayload(payload);
|
|
4355
|
+
const screen = getCurrentScreen();
|
|
4356
|
+
const probe = probeLoadingState(targetId || null, screen);
|
|
4357
|
+
return {
|
|
4358
|
+
fingerprint: probe.fingerprint,
|
|
4359
|
+
fields: discreteFieldSignature(),
|
|
4360
|
+
screen,
|
|
4361
|
+
readable: probe.fingerprint !== ""
|
|
4362
|
+
};
|
|
4363
|
+
}
|
|
4364
|
+
async function waitForPostInteractionSettle(payload, preBaseline) {
|
|
3746
4365
|
const targetId = targetIdFromPayload(payload);
|
|
3747
|
-
const
|
|
3748
|
-
const
|
|
3749
|
-
const baselineFields =
|
|
3750
|
-
const readable = baseline.
|
|
4366
|
+
const baseline = preBaseline ?? capturePreInteractionBaseline(payload);
|
|
4367
|
+
const fromScreen = baseline.screen;
|
|
4368
|
+
const baselineFields = baseline.fields;
|
|
4369
|
+
const readable = baseline.readable;
|
|
3751
4370
|
await sleepMs(100);
|
|
3752
4371
|
const afterPress = probeLoadingState(targetId || null, getCurrentScreen());
|
|
3753
4372
|
const modalOrLoading = afterPress.modalOpen || afterPress.loading || afterPress.pressedDisabled;
|
|
@@ -3759,7 +4378,8 @@ async function waitForPostInteractionSettle(payload) {
|
|
|
3759
4378
|
if (discreteFieldSignature() !== baselineFields) return { effect: "changed" };
|
|
3760
4379
|
return { effect: settled.fingerprint !== baseline.fingerprint ? "changed" : "none" };
|
|
3761
4380
|
};
|
|
3762
|
-
const
|
|
4381
|
+
const declared = declaredBudgetMs(resolveActionHints(fromScreen, targetId || null, payload));
|
|
4382
|
+
const capMs = declared ?? (isListItemPressTarget(targetId) ? 5e3 : 1e4);
|
|
3763
4383
|
if (modalOrLoading || fingerprintChanged) {
|
|
3764
4384
|
await waitForLoadingSettle({
|
|
3765
4385
|
pressedComponentId: targetId || null,
|
|
@@ -3778,12 +4398,6 @@ async function waitForPostInteractionSettle(payload) {
|
|
|
3778
4398
|
}
|
|
3779
4399
|
|
|
3780
4400
|
// src/executor/ActionExecutor.ts
|
|
3781
|
-
var DEFAULT_PERMISSIONS = {
|
|
3782
|
-
canNavigate: true,
|
|
3783
|
-
canFillForms: true,
|
|
3784
|
-
canInteractUI: true,
|
|
3785
|
-
canSubmitForms: true
|
|
3786
|
-
};
|
|
3787
4401
|
function validateActionPayload(action) {
|
|
3788
4402
|
const invalid = (message, ids) => ({
|
|
3789
4403
|
success: false,
|
|
@@ -3854,7 +4468,7 @@ async function executeAction(action, context) {
|
|
|
3854
4468
|
appilotsDebugLog(
|
|
3855
4469
|
`executeAction called: type="${action.type}", id="${action.id}", payload=${JSON.stringify(action.payload)}`
|
|
3856
4470
|
);
|
|
3857
|
-
const permissions = context.permissions
|
|
4471
|
+
const permissions = resolveAgentPermissions(context.permissions);
|
|
3858
4472
|
context.emit({
|
|
3859
4473
|
type: "agent:action:start",
|
|
3860
4474
|
timestamp: Date.now(),
|
|
@@ -3912,12 +4526,13 @@ async function executeAction(action, context) {
|
|
|
3912
4526
|
break;
|
|
3913
4527
|
case "ui_interaction": {
|
|
3914
4528
|
const payload = action.payload;
|
|
4529
|
+
const baseline = capturePreInteractionBaseline(payload);
|
|
3915
4530
|
result = uiInteractionHandler(payload, {
|
|
3916
4531
|
permissions,
|
|
3917
4532
|
registry: context.registry ?? getDefaultRegistry()
|
|
3918
4533
|
});
|
|
3919
4534
|
if (result.success) {
|
|
3920
|
-
const settle = await waitForPostInteractionSettle(payload);
|
|
4535
|
+
const settle = await waitForPostInteractionSettle(payload, baseline);
|
|
3921
4536
|
result.effect = result.effect ?? effectFor(payload, settle.effect);
|
|
3922
4537
|
}
|
|
3923
4538
|
break;
|
|
@@ -4005,18 +4620,28 @@ var _patchedAlert = (title, message, buttons, options) => {
|
|
|
4005
4620
|
return suppression.originalAlert(title, message, buttons, options);
|
|
4006
4621
|
}
|
|
4007
4622
|
suppression.consumed = true;
|
|
4623
|
+
const report = (handlerCompleted) => suppression.onHandled?.({
|
|
4624
|
+
title: title.slice(0, 300),
|
|
4625
|
+
message: message?.slice(0, 1e3),
|
|
4626
|
+
buttonLabel: button.text?.slice(0, 160),
|
|
4627
|
+
handlerCompleted
|
|
4628
|
+
});
|
|
4008
4629
|
appilotsDebugLog("Suppressing native Alert during already-confirmed destructive agent action");
|
|
4009
4630
|
try {
|
|
4010
|
-
suppression.pending = Promise.resolve(button.onPress()).then(() =>
|
|
4631
|
+
suppression.pending = Promise.resolve(button.onPress()).then(() => report(true)).catch((err) => {
|
|
4632
|
+
reportHandlerFailure(err);
|
|
4633
|
+
report(false);
|
|
4634
|
+
});
|
|
4011
4635
|
} catch (err) {
|
|
4012
4636
|
reportHandlerFailure(err);
|
|
4013
4637
|
suppression.pending = null;
|
|
4638
|
+
report(false);
|
|
4014
4639
|
}
|
|
4015
4640
|
};
|
|
4016
4641
|
function reportHandlerFailure(err) {
|
|
4017
4642
|
console.error("[Appilots] The app\u2019s Alert handler threw while being auto-confirmed", err);
|
|
4018
4643
|
}
|
|
4019
|
-
async function runWithConfirmedDestructiveContext(enabled, host, fn) {
|
|
4644
|
+
async function runWithConfirmedDestructiveContext(enabled, host, fn, onHandled) {
|
|
4020
4645
|
if (!enabled) return fn();
|
|
4021
4646
|
if (_active) {
|
|
4022
4647
|
_active.depth += 1;
|
|
@@ -4037,6 +4662,7 @@ async function runWithConfirmedDestructiveContext(enabled, host, fn) {
|
|
|
4037
4662
|
depth: 1,
|
|
4038
4663
|
consumed: false,
|
|
4039
4664
|
pending: null,
|
|
4665
|
+
onHandled,
|
|
4040
4666
|
timer: null
|
|
4041
4667
|
};
|
|
4042
4668
|
_active = suppression;
|
|
@@ -4056,7 +4682,7 @@ async function runWithConfirmedDestructiveContext(enabled, host, fn) {
|
|
|
4056
4682
|
}
|
|
4057
4683
|
|
|
4058
4684
|
// src/platform/reactNativeAdapter.ts
|
|
4059
|
-
var
|
|
4685
|
+
var deltaTracker = createSnapshotDeltaTracker();
|
|
4060
4686
|
function resolveScreenMetadata() {
|
|
4061
4687
|
const activePath = getActiveRouteNames();
|
|
4062
4688
|
for (let i = activePath.length - 1; i >= 0; i--) {
|
|
@@ -4070,8 +4696,14 @@ function resolveScreenMetadata() {
|
|
|
4070
4696
|
return screen ? getScreenMetadata(screen) : void 0;
|
|
4071
4697
|
}
|
|
4072
4698
|
function buildAgentContext(extras = {}) {
|
|
4699
|
+
if (extras.newMission === true) {
|
|
4700
|
+
deltaTracker.reset();
|
|
4701
|
+
for (const entry of listRegistry.snapshot()) entry.exploration?.reset();
|
|
4702
|
+
}
|
|
4073
4703
|
const context = {};
|
|
4074
4704
|
context.platform = "react-native";
|
|
4705
|
+
context.missionProtocol = 1;
|
|
4706
|
+
context.deviceLocale = detectDeviceLocale() ?? "en";
|
|
4075
4707
|
const screen = getCurrentScreen();
|
|
4076
4708
|
if (screen) context.currentScreen = screen;
|
|
4077
4709
|
const navigationState = getNavigationStateSnapshot();
|
|
@@ -4079,7 +4711,16 @@ function buildAgentContext(extras = {}) {
|
|
|
4079
4711
|
const screenMeta = resolveScreenMetadata();
|
|
4080
4712
|
if (screenMeta) context.screenMetadata = screenMeta;
|
|
4081
4713
|
try {
|
|
4082
|
-
|
|
4714
|
+
const snapshot = captureSnapshot();
|
|
4715
|
+
for (const list of snapshot.lists) {
|
|
4716
|
+
const memory = list.id ? listRegistry.get(list.id)?.exploration : void 0;
|
|
4717
|
+
if (!memory) continue;
|
|
4718
|
+
memory.observe(list.items);
|
|
4719
|
+
list.exploration = memory.snapshot();
|
|
4720
|
+
}
|
|
4721
|
+
const delta = deltaTracker.observe(snapshot);
|
|
4722
|
+
if (delta) snapshot.delta = delta;
|
|
4723
|
+
context.snapshot = snapshot;
|
|
4083
4724
|
} catch (err) {
|
|
4084
4725
|
appilotsDebugWarn("buildAgentContext: captureSnapshot failed", err);
|
|
4085
4726
|
}
|
|
@@ -4088,40 +4729,40 @@ function buildAgentContext(extras = {}) {
|
|
|
4088
4729
|
if (filtered.length > 0) context.registeredComponents = filtered;
|
|
4089
4730
|
const registeredLists = listRegistry.snapshot();
|
|
4090
4731
|
if (registeredLists.length > 0) {
|
|
4091
|
-
|
|
4092
|
-
|
|
4732
|
+
const snapshot = context.snapshot;
|
|
4733
|
+
const activeIds = new Set(
|
|
4734
|
+
[...snapshot?.lists ?? [], ...snapshot?.scrollables ?? []].map((l) => l.id)
|
|
4093
4735
|
);
|
|
4736
|
+
context.registeredLists = registeredLists.filter((entry) => activeIds.has(entry.id)).map(({ id, kind, component, itemCount, totalItemCount, label, refreshing, empty }) => ({
|
|
4737
|
+
id,
|
|
4738
|
+
kind,
|
|
4739
|
+
component,
|
|
4740
|
+
itemCount,
|
|
4741
|
+
totalItemCount,
|
|
4742
|
+
label,
|
|
4743
|
+
refreshing,
|
|
4744
|
+
empty
|
|
4745
|
+
}));
|
|
4094
4746
|
}
|
|
4095
4747
|
if (extras.loadingPending === true) context.loadingPending = true;
|
|
4096
4748
|
return context;
|
|
4097
4749
|
}
|
|
4098
|
-
function resolveActionHints(screen, pressedComponentId, actionPayload) {
|
|
4099
|
-
if (actionPayload && typeof actionPayload === "object") {
|
|
4100
|
-
const inline = actionPayload._appilotsInferred;
|
|
4101
|
-
if (inline && typeof inline === "object") {
|
|
4102
|
-
return inline;
|
|
4103
|
-
}
|
|
4104
|
-
}
|
|
4105
|
-
if (!screen || !pressedComponentId) return void 0;
|
|
4106
|
-
const meta = getScreenMetadata(screen);
|
|
4107
|
-
const actions = meta?.actions;
|
|
4108
|
-
if (!actions || actions.length === 0) return void 0;
|
|
4109
|
-
for (const a of actions) {
|
|
4110
|
-
if (typeof a === "string") continue;
|
|
4111
|
-
if (idLooselyMatches(a.id, pressedComponentId)) {
|
|
4112
|
-
const inferred = a.appilotsInferred;
|
|
4113
|
-
if (inferred && typeof inferred === "object") return inferred;
|
|
4114
|
-
return void 0;
|
|
4115
|
-
}
|
|
4116
|
-
}
|
|
4117
|
-
return void 0;
|
|
4118
|
-
}
|
|
4119
4750
|
async function settleTurn(input) {
|
|
4120
4751
|
const toolWithoutEffectIds = /* @__PURE__ */ new Set();
|
|
4121
4752
|
let loadingPending = false;
|
|
4122
4753
|
const { turnActions, pressedComponentId: pressedId, navigateTargetScreen } = input;
|
|
4123
4754
|
const successById = new Map(input.results.map((r) => [r.actionId, r.success]));
|
|
4755
|
+
const effectById = new Map(input.results.map((r) => [r.actionId, r.effect]));
|
|
4124
4756
|
const typeById = new Map(input.results.map((r) => [r.actionId, r.type]));
|
|
4757
|
+
const pressedAction = turnActions.find(
|
|
4758
|
+
(a) => a.type === "ui_interaction" && a.payload?.componentId === pressedId
|
|
4759
|
+
);
|
|
4760
|
+
const hints = resolveActionHints(
|
|
4761
|
+
getCurrentScreen(),
|
|
4762
|
+
pressedId,
|
|
4763
|
+
pressedAction?.payload
|
|
4764
|
+
);
|
|
4765
|
+
const capSource = typeof hints?.asyncBudgetMs === "number" && hints.asyncBudgetMs > 0 ? " (app-declared)" : hints?.isAsyncTrigger ? " (mcp-inferred)" : "";
|
|
4125
4766
|
if (input.hadNavigate) {
|
|
4126
4767
|
const settle = await waitForScreenSettle({
|
|
4127
4768
|
expectingChange: true,
|
|
@@ -4142,27 +4783,26 @@ async function settleTurn(input) {
|
|
|
4142
4783
|
if (type === "navigate") toolWithoutEffectIds.add(actionId);
|
|
4143
4784
|
}
|
|
4144
4785
|
}
|
|
4786
|
+
const postNavigateCapMs = resolvePostNavigateCapMs(hints);
|
|
4145
4787
|
const loadSettle = await waitForLoadingSettle({
|
|
4146
4788
|
fromScreen: settle.screen ?? null,
|
|
4147
|
-
maxMs:
|
|
4789
|
+
maxMs: postNavigateCapMs
|
|
4148
4790
|
});
|
|
4149
4791
|
appilotsDebugLog(
|
|
4150
|
-
`settleTurn: post-navigate loading settle \u2014 settled=${loadSettle.settled} loadingPending=${loadSettle.loadingPending} probes=${loadSettle.probeCount} waitedMs=${loadSettle.waitedMs}`
|
|
4792
|
+
`settleTurn: post-navigate loading settle \u2014 settled=${loadSettle.settled} loadingPending=${loadSettle.loadingPending} probes=${loadSettle.probeCount} waitedMs=${loadSettle.waitedMs} cap=${postNavigateCapMs}${capSource}`
|
|
4151
4793
|
);
|
|
4152
4794
|
if (loadSettle.loadingPending) loadingPending = true;
|
|
4153
4795
|
} else {
|
|
4154
|
-
const
|
|
4155
|
-
|
|
4156
|
-
);
|
|
4157
|
-
const hints = resolveActionHints(getCurrentScreen(), pressedId, pressedAction?.payload);
|
|
4158
|
-
const settleCapMs = hints?.isAsyncTrigger === true ? 1e4 : POST_ACTION_LOADING_MAX_MS;
|
|
4796
|
+
const settleCapMs = resolveSettleCapMs(hints);
|
|
4797
|
+
const busyMaxMs = resolveBusyCeilingMs(hints);
|
|
4159
4798
|
const loadSettle = await waitForLoadingSettle({
|
|
4160
4799
|
pressedComponentId: pressedId,
|
|
4161
4800
|
fromScreen: getCurrentScreen(),
|
|
4162
|
-
maxMs: settleCapMs
|
|
4801
|
+
maxMs: settleCapMs,
|
|
4802
|
+
...busyMaxMs !== void 0 ? { busyMaxMs } : {}
|
|
4163
4803
|
});
|
|
4164
4804
|
appilotsDebugLog(
|
|
4165
|
-
`settleTurn: post-action loading settle \u2014 pressedId="${pressedId ?? ""}" settled=${loadSettle.settled} transitioned=${loadSettle.transitioned} loadingPending=${loadSettle.loadingPending} probes=${loadSettle.probeCount} waitedMs=${loadSettle.waitedMs} cap=${settleCapMs}${
|
|
4805
|
+
`settleTurn: post-action loading settle \u2014 pressedId="${pressedId ?? ""}" settled=${loadSettle.settled} transitioned=${loadSettle.transitioned} loadingPending=${loadSettle.loadingPending} probes=${loadSettle.probeCount} waitedMs=${loadSettle.waitedMs} cap=${settleCapMs}${capSource}${busyMaxMs !== void 0 ? ` busyCap=${busyMaxMs}` : ""}`
|
|
4166
4806
|
);
|
|
4167
4807
|
if (loadSettle.loadingPending) loadingPending = true;
|
|
4168
4808
|
const listPressActions = turnActions.filter((action) => {
|
|
@@ -4188,6 +4828,7 @@ async function settleTurn(input) {
|
|
|
4188
4828
|
);
|
|
4189
4829
|
if (!postPressSettle.transitioned && baselineFingerprint === afterFingerprint) {
|
|
4190
4830
|
for (const action of listPressActions) {
|
|
4831
|
+
if (effectById.get(action.id) === "changed") continue;
|
|
4191
4832
|
if (successById.get(action.id)) {
|
|
4192
4833
|
toolWithoutEffectIds.add(action.id);
|
|
4193
4834
|
}
|
|
@@ -4205,18 +4846,40 @@ var reactNativeChatAdapter = {
|
|
|
4205
4846
|
};
|
|
4206
4847
|
function createReactNativeActionRunner(getOptions) {
|
|
4207
4848
|
return {
|
|
4849
|
+
buildMissionContext: () => reactNativeChatAdapter.buildContext(),
|
|
4208
4850
|
async execute(action, { confirmedDestructive }) {
|
|
4209
|
-
const { permissions, emit, navigationRef, suppressNativeConfirm } = getOptions();
|
|
4851
|
+
const { permissions, emit: emit2, navigationRef, suppressNativeConfirm } = getOptions();
|
|
4210
4852
|
const navRef = navigationRef?.current ? navigationRef : { current: getNavigationRef() };
|
|
4211
|
-
|
|
4853
|
+
let receipt;
|
|
4854
|
+
let terminal;
|
|
4855
|
+
const result = await runWithConfirmedDestructiveContext(
|
|
4212
4856
|
confirmedDestructive && suppressNativeConfirm !== false,
|
|
4213
4857
|
Alert,
|
|
4214
4858
|
() => executeAction(action, {
|
|
4215
4859
|
navigationRef: navRef,
|
|
4216
4860
|
permissions,
|
|
4217
|
-
emit
|
|
4218
|
-
|
|
4861
|
+
emit: (event) => {
|
|
4862
|
+
if (confirmedDestructive && (event.type === "agent:action:complete" || event.type === "agent:action:error"))
|
|
4863
|
+
terminal = event;
|
|
4864
|
+
else emit2(event);
|
|
4865
|
+
}
|
|
4866
|
+
}),
|
|
4867
|
+
(handled) => {
|
|
4868
|
+
receipt = handled;
|
|
4869
|
+
}
|
|
4219
4870
|
);
|
|
4871
|
+
const failed = receipt?.handlerCompleted === false;
|
|
4872
|
+
if (terminal)
|
|
4873
|
+
emit2({
|
|
4874
|
+
...terminal,
|
|
4875
|
+
type: failed ? "agent:action:error" : terminal.type,
|
|
4876
|
+
data: {
|
|
4877
|
+
...terminal.data,
|
|
4878
|
+
...receipt ? { nativeConfirmation: receipt } : {},
|
|
4879
|
+
...failed ? { success: false, error: "Native confirmation callback failed" } : {}
|
|
4880
|
+
}
|
|
4881
|
+
});
|
|
4882
|
+
return failed ? { ...result, success: false, error: "Native confirmation callback failed" } : result;
|
|
4220
4883
|
},
|
|
4221
4884
|
getCurrentScreen,
|
|
4222
4885
|
getCurrentScreenSignature,
|
|
@@ -4229,16 +4892,16 @@ function createReactNativeActionRunner(getOptions) {
|
|
|
4229
4892
|
|
|
4230
4893
|
// src/hooks/useAppilotsChat.ts
|
|
4231
4894
|
function useAppilotsChat(options = {}) {
|
|
4232
|
-
const { client, emit, subscribe } = useAppilotsContext();
|
|
4895
|
+
const { client, emit: emit2, subscribe } = useAppilotsContext();
|
|
4233
4896
|
const machine = useMemo(
|
|
4234
4897
|
() => new ChatSessionMachine(
|
|
4235
|
-
{ client, adapter: reactNativeChatAdapter, emit, warn: appilotsDebugWarn },
|
|
4898
|
+
{ client, adapter: reactNativeChatAdapter, emit: emit2, warn: appilotsDebugWarn },
|
|
4236
4899
|
options
|
|
4237
4900
|
),
|
|
4238
4901
|
// A new machine per client/provider instance — options are pushed in
|
|
4239
4902
|
// below so changing copy never resets the transcript.
|
|
4240
4903
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
4241
|
-
[client,
|
|
4904
|
+
[client, emit2]
|
|
4242
4905
|
);
|
|
4243
4906
|
const optionsRef = useRef(options);
|
|
4244
4907
|
optionsRef.current = options;
|
|
@@ -4246,6 +4909,14 @@ function useAppilotsChat(options = {}) {
|
|
|
4246
4909
|
const state = useSyncExternalStore(machine.subscribeState, machine.getState, machine.getState);
|
|
4247
4910
|
useEffect(() => subscribe(machine.handleEvent), [subscribe, machine]);
|
|
4248
4911
|
useEffect(() => () => machine.dispose(), [machine]);
|
|
4912
|
+
useEffect(() => {
|
|
4913
|
+
void machine.restoreMission();
|
|
4914
|
+
const subscription = AppState?.addEventListener?.("change", (next) => {
|
|
4915
|
+
if (next !== "active" && machine.getState().mission?.status === "running")
|
|
4916
|
+
void machine.pauseMission();
|
|
4917
|
+
});
|
|
4918
|
+
return () => subscription?.remove();
|
|
4919
|
+
}, [machine]);
|
|
4249
4920
|
const requestHuman = useCallback(
|
|
4250
4921
|
(reason) => machine.requestHuman(reason),
|
|
4251
4922
|
[machine]
|
|
@@ -4255,7 +4926,13 @@ function useAppilotsChat(options = {}) {
|
|
|
4255
4926
|
const clearMessages = useCallback(() => machine.clearMessages(), [machine]);
|
|
4256
4927
|
const clearError = useCallback(() => machine.clearError(), [machine]);
|
|
4257
4928
|
return {
|
|
4929
|
+
mission: state.mission,
|
|
4930
|
+
pauseMission: () => machine.pauseMission(),
|
|
4931
|
+
resumeMission: () => machine.resumeMission(),
|
|
4932
|
+
cancelMission: () => machine.cancelMission(),
|
|
4933
|
+
hasStartedActing: state.hasStartedActing,
|
|
4258
4934
|
messages: state.messages,
|
|
4935
|
+
replyLocale: state.replyLocale,
|
|
4259
4936
|
isLoading: state.isLoading,
|
|
4260
4937
|
loadingStatusKey: state.loadingStatusKey,
|
|
4261
4938
|
error: state.error,
|
|
@@ -4268,24 +4945,18 @@ function useAppilotsChat(options = {}) {
|
|
|
4268
4945
|
clearError
|
|
4269
4946
|
};
|
|
4270
4947
|
}
|
|
4271
|
-
var DEFAULT_PERMISSIONS2 = {
|
|
4272
|
-
canNavigate: true,
|
|
4273
|
-
canFillForms: true,
|
|
4274
|
-
canInteractUI: true,
|
|
4275
|
-
canSubmitForms: true
|
|
4276
|
-
};
|
|
4277
4948
|
function useAppilotsActions(options = {}) {
|
|
4278
|
-
const { client, subscribe, config, emit } = useAppilotsContext();
|
|
4949
|
+
const { client, subscribe, config, emit: emit2 } = useAppilotsContext();
|
|
4279
4950
|
const { navigationRef, autoExecute = false } = options;
|
|
4280
4951
|
const runnerOptionsRef = useRef({
|
|
4281
|
-
permissions: config.permissions
|
|
4282
|
-
emit,
|
|
4952
|
+
permissions: resolveAgentPermissions(config.permissions),
|
|
4953
|
+
emit: emit2,
|
|
4283
4954
|
navigationRef,
|
|
4284
4955
|
suppressNativeConfirm: config.suppressNativeConfirm
|
|
4285
4956
|
});
|
|
4286
4957
|
runnerOptionsRef.current = {
|
|
4287
|
-
permissions: config.permissions
|
|
4288
|
-
emit,
|
|
4958
|
+
permissions: resolveAgentPermissions(config.permissions),
|
|
4959
|
+
emit: emit2,
|
|
4289
4960
|
navigationRef,
|
|
4290
4961
|
suppressNativeConfirm: config.suppressNativeConfirm
|
|
4291
4962
|
};
|
|
@@ -4294,13 +4965,13 @@ function useAppilotsActions(options = {}) {
|
|
|
4294
4965
|
{
|
|
4295
4966
|
client,
|
|
4296
4967
|
adapter: createReactNativeActionRunner(() => runnerOptionsRef.current),
|
|
4297
|
-
emit,
|
|
4968
|
+
emit: emit2,
|
|
4298
4969
|
warn: appilotsDebugWarn
|
|
4299
4970
|
},
|
|
4300
4971
|
{ autoExecute }
|
|
4301
4972
|
),
|
|
4302
4973
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
4303
|
-
[client,
|
|
4974
|
+
[client, emit2]
|
|
4304
4975
|
);
|
|
4305
4976
|
machine.setOptions({ autoExecute });
|
|
4306
4977
|
const state = useSyncExternalStore(machine.subscribeState, machine.getState, machine.getState);
|
|
@@ -4390,9 +5061,9 @@ function useSuggestedPrompts(options) {
|
|
|
4390
5061
|
if (!enabled) return;
|
|
4391
5062
|
let lastScreen = getCurrentScreen();
|
|
4392
5063
|
const interval = setInterval(() => {
|
|
4393
|
-
const
|
|
4394
|
-
if (
|
|
4395
|
-
lastScreen =
|
|
5064
|
+
const current2 = getCurrentScreen();
|
|
5065
|
+
if (current2 !== lastScreen) {
|
|
5066
|
+
lastScreen = current2;
|
|
4396
5067
|
setRefreshTick((t) => t + 1);
|
|
4397
5068
|
}
|
|
4398
5069
|
}, 800);
|
|
@@ -4405,7 +5076,7 @@ function useSuggestedPrompts(options) {
|
|
|
4405
5076
|
};
|
|
4406
5077
|
}
|
|
4407
5078
|
function useAppilotsNavigation() {
|
|
4408
|
-
const { emit } = useAppilotsContext();
|
|
5079
|
+
const { emit: emit2 } = useAppilotsContext();
|
|
4409
5080
|
const [currentScreen, setCurrentScreenState] = useState(null);
|
|
4410
5081
|
const [navigationHistory, setNavigationHistory] = useState([]);
|
|
4411
5082
|
const navigationRef = useRef(null);
|
|
@@ -4414,13 +5085,13 @@ function useAppilotsNavigation() {
|
|
|
4414
5085
|
setCurrentScreenState(screenName);
|
|
4415
5086
|
setCurrentScreen(screenName);
|
|
4416
5087
|
setNavigationHistory((prev) => [...prev, screenName]);
|
|
4417
|
-
|
|
5088
|
+
emit2({
|
|
4418
5089
|
type: "navigation:change",
|
|
4419
5090
|
timestamp: Date.now(),
|
|
4420
5091
|
data: { screenName }
|
|
4421
5092
|
});
|
|
4422
5093
|
},
|
|
4423
|
-
[
|
|
5094
|
+
[emit2]
|
|
4424
5095
|
);
|
|
4425
5096
|
useEffect(() => {
|
|
4426
5097
|
const nav = navigationRef.current;
|
|
@@ -4470,6 +5141,11 @@ function useAppilots() {
|
|
|
4470
5141
|
rejectAction: actions.rejectAction
|
|
4471
5142
|
};
|
|
4472
5143
|
}
|
|
5144
|
+
function useAppilotsElementProps(id) {
|
|
5145
|
+
return useMemo(() => ({ testID: id }), [id]);
|
|
5146
|
+
}
|
|
5147
|
+
|
|
5148
|
+
// src/hooks/useAppilotsField.ts
|
|
4473
5149
|
function useAppilotsField(id, options) {
|
|
4474
5150
|
const { value, onChangeText, ref, fieldType, label, screen } = options;
|
|
4475
5151
|
const registry = useResolvedRegistry();
|
|
@@ -4497,6 +5173,7 @@ function useAppilotsField(id, options) {
|
|
|
4497
5173
|
registry.unregister(id);
|
|
4498
5174
|
};
|
|
4499
5175
|
}, [id, registry, getValue, setValue, focus, ref, fieldType, label, screen]);
|
|
5176
|
+
return useAppilotsElementProps(id);
|
|
4500
5177
|
}
|
|
4501
5178
|
function useAppilotsTarget(id, options) {
|
|
4502
5179
|
const { onPress, onLongPress, onScrollTo, label, screen } = options;
|
|
@@ -4524,6 +5201,7 @@ function useAppilotsTarget(id, options) {
|
|
|
4524
5201
|
registry.unregister(id);
|
|
4525
5202
|
};
|
|
4526
5203
|
}, [id, registry, press, longPress, scrollTo, onLongPress, onScrollTo, label, screen]);
|
|
5204
|
+
return useAppilotsElementProps(id);
|
|
4527
5205
|
}
|
|
4528
5206
|
function useAppilotsToggle(id, options) {
|
|
4529
5207
|
const { value, onValueChange, label, screen } = options;
|
|
@@ -4547,6 +5225,7 @@ function useAppilotsToggle(id, options) {
|
|
|
4547
5225
|
registry.unregister(id);
|
|
4548
5226
|
};
|
|
4549
5227
|
}, [id, registry, getValue, setValue, label, screen]);
|
|
5228
|
+
return useAppilotsElementProps(id);
|
|
4550
5229
|
}
|
|
4551
5230
|
function useAppilotsSlider(id, options) {
|
|
4552
5231
|
const { value, onValueChange, min, max, step, label, screen } = options;
|
|
@@ -4573,6 +5252,7 @@ function useAppilotsSlider(id, options) {
|
|
|
4573
5252
|
registry.unregister(id);
|
|
4574
5253
|
};
|
|
4575
5254
|
}, [id, registry, getValue, setValue, min, max, step, label, screen]);
|
|
5255
|
+
return useAppilotsElementProps(id);
|
|
4576
5256
|
}
|
|
4577
5257
|
|
|
4578
|
-
export { appilotsSelfCheck, captureSnapshot, executeAction, shouldShowSuggestedPrompts, useAppilots, useAppilotsActions, useAppilotsChat, useAppilotsField, useAppilotsNavigation, useAppilotsSlider, useAppilotsTarget, useAppilotsToggle, useSuggestedPrompts };
|
|
5258
|
+
export { AppilotsI18nProvider, REVEAL_MARGIN, SPOTLIGHT_TTL_MS, appilotsSelfCheck, captureSnapshot, detectDeviceLocale, executeAction, getSpotlightMode, getSpotlightTarget, offsetToReveal, resolveLocale, revealTarget, setSpotlightMode, setSpotlightTarget, shouldShowSuggestedPrompts, subscribeSpotlight, useAppilots, useAppilotsActions, useAppilotsChat, useAppilotsField, useAppilotsI18n, useAppilotsNavigation, useAppilotsSlider, useAppilotsTarget, useAppilotsToggle, useSuggestedPrompts };
|