@appilots/sdk 0.11.3 → 0.14.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.
@@ -1,6 +1,277 @@
1
- import { createSnapshotDeltaTracker, 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, listRegistry, getOpenNativeDialog, componentRegistry, getLastAnsweredNativeDialog, probeLoadingState, isListItemPressTarget, waitForLoadingSettle, describeValue, waitForScreenSettle, screenDepartedBaseline, routesBelongToSameFeature, actionPressTargetId, getNavigationStateSnapshot, getNavigationRef, isOptionalStepAutomationFailure, OPTIONAL_STEP_AUTOMATION_HINT, isSwitchLike, idLooselyMatches, normalize3, matchScore, FORM_VALIDATION_TEXT_RE, parseStableElementId, shouldSkipSubtree, isAlwaysListContainer, isHeuristicListContainer, findListItems, getTypeName, foldForCompare, isModalFiber, switchValue, getAllScreens, snapshotShowsOpenCreateForm, isReferenceable, looksDestructiveActionLabel, isGenericSelectValue, FORM_WRITE_SUCCESS_TEXT_RE, TOUCHABLE_NAMES } from './chunk-ROXXCDIG.mjs';
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, isAppilotsSensitiveMarked, isGenericSelectValue, SUBMIT_CONTROL_RE, isVisibleValidationText, FORM_WRITE_SUCCESS_TEXT_RE, TOUCHABLE_NAMES } from './chunk-AKAMWKO6.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
+ stillWorking: "Ainda trabalhando\u2026 {seconds} s",
17
+ poweredBy: "Powered by Appilots",
18
+ approve: "Aprovar",
19
+ reject: "Rejeitar",
20
+ cancelledByUser: "Cancelado pelo usu\xE1rio",
21
+ confirmDestructiveTitle: "A\xE7\xE3o destrutiva",
22
+ confirmTitle: "Confirma\xE7\xE3o necess\xE1ria",
23
+ confirmMessage: 'Tem certeza que deseja "{action}"?',
24
+ confirmDestructiveApprove: "Sim, executar",
25
+ confirmApprove: "Confirmar",
26
+ confirmCancel: "Cancelar",
27
+ confirmThisAction: "esta a\xE7\xE3o",
28
+ confirmExpired: "Esta confirma\xE7\xE3o expirou. Pe\xE7a de novo ao assistente para continuar.",
29
+ unknownAction: "A\xE7\xE3o desconhecida",
30
+ somethingWentWrong: "Algo deu errado",
31
+ openChatA11y: "Abrir chat do assistente",
32
+ closeChatA11y: "Fechar chat do assistente",
33
+ stopGeneratingA11y: "Parar gera\xE7\xE3o",
34
+ talkToHumanA11y: "Falar com um atendente humano",
35
+ humanAgentLabel: "Atendente",
36
+ escalationRequested: "Certo! Chamei um atendente humano. Voc\xEA pode continuar usando o app normalmente enquanto isso.",
37
+ escalationConnected: "Um atendente entrou na conversa.",
38
+ escalationResolved: "O atendimento humano foi encerrado. Voltei a te ajudar por aqui.",
39
+ escalationOffer: "Se preferir, posso chamar uma pessoa para ajudar.",
40
+ escalationOfferChip: "Falar com um atendente",
41
+ escalationPendingBanner: "Aguardando um atendente...",
42
+ escalationActiveBanner: "Voc\xEA est\xE1 falando com um atendente",
43
+ pillWorking: "Trabalhando...",
44
+ pillExpandA11y: "Abrir a conversa do assistente",
45
+ pillStopA11y: "Parar o assistente",
46
+ missionPause: "Pausar",
47
+ missionResume: "Retomar tarefa",
48
+ missionCancel: "Cancelar tarefa",
49
+ missionPaused: "Pausada",
50
+ pillDone: "Pronto \u2014 toque para ver",
51
+ a11yAwaitingApproval: "O assistente precisa da sua confirma\xE7\xE3o para continuar.",
52
+ a11yTurnDone: "O assistente terminou.",
53
+ micStartA11y: "Segurar para falar",
54
+ micStopA11y: "Soltar para enviar",
55
+ micListening: "Ouvindo..."
56
+ };
57
+
58
+ // src/i18n/bundles/en.ts
59
+ var en = {
60
+ chatTitle: "Appilots Assistant",
61
+ clearButton: "Clear",
62
+ inputPlaceholder: "Type a message...",
63
+ emptyTitle: "How can I help?",
64
+ emptySubtitle: "I navigate, fill forms, and run actions in this app.",
65
+ thinking: "Thinking...",
66
+ statusAnalyzing: "Reading the screen...",
67
+ statusWaitingApp: "Waiting for the app...",
68
+ statusAdjusting: "Adjusting the plan...",
69
+ stillWorking: "Still working\u2026 {seconds} s",
70
+ poweredBy: "Powered by Appilots",
71
+ approve: "Approve",
72
+ reject: "Reject",
73
+ cancelledByUser: "Cancelled by user",
74
+ confirmDestructiveTitle: "Destructive action",
75
+ confirmTitle: "Confirmation needed",
76
+ confirmMessage: 'Are you sure you want to "{action}"?',
77
+ confirmDestructiveApprove: "Yes, do it",
78
+ confirmApprove: "Confirm",
79
+ confirmCancel: "Cancel",
80
+ confirmThisAction: "this action",
81
+ confirmExpired: "This confirmation expired. Ask the assistant again to continue.",
82
+ unknownAction: "Unknown action",
83
+ somethingWentWrong: "Something went wrong",
84
+ openChatA11y: "Open assistant chat",
85
+ closeChatA11y: "Close assistant chat",
86
+ stopGeneratingA11y: "Stop generating",
87
+ talkToHumanA11y: "Talk to a human agent",
88
+ humanAgentLabel: "Agent",
89
+ escalationRequested: "Done! I've called a human agent. You can keep using the app normally in the meantime.",
90
+ escalationConnected: "A human agent joined the conversation.",
91
+ escalationResolved: "The human handoff has ended. I'm back to help you here.",
92
+ escalationOffer: "If you prefer, I can bring in a person to help.",
93
+ escalationOfferChip: "Talk to a human",
94
+ escalationPendingBanner: "Waiting for a human agent...",
95
+ escalationActiveBanner: "You're talking to a human agent",
96
+ pillWorking: "Working...",
97
+ pillExpandA11y: "Open the assistant conversation",
98
+ pillStopA11y: "Stop the assistant",
99
+ missionPause: "Pause",
100
+ missionResume: "Resume task",
101
+ missionCancel: "Cancel task",
102
+ missionPaused: "Paused",
103
+ pillDone: "Done \u2014 tap to read",
104
+ a11yAwaitingApproval: "The assistant needs your confirmation to continue.",
105
+ a11yTurnDone: "The assistant finished.",
106
+ micStartA11y: "Hold to talk",
107
+ micStopA11y: "Release to send",
108
+ micListening: "Listening..."
109
+ };
110
+
111
+ // src/i18n/bundles/es.ts
112
+ var es = {
113
+ chatTitle: "Asistente Appilots",
114
+ clearButton: "Limpiar",
115
+ inputPlaceholder: "Escribe un mensaje...",
116
+ emptyTitle: "\xBFEn qu\xE9 puedo ayudarte?",
117
+ emptySubtitle: "Navego, relleno formularios y ejecuto acciones en esta app.",
118
+ thinking: "Pensando...",
119
+ statusAnalyzing: "Analizando la pantalla...",
120
+ statusWaitingApp: "Esperando a la app...",
121
+ statusAdjusting: "Ajustando el plan...",
122
+ stillWorking: "Sigo trabajando\u2026 {seconds} s",
123
+ poweredBy: "Hecho con Appilots",
124
+ approve: "Aprobar",
125
+ reject: "Rechazar",
126
+ cancelledByUser: "Cancelado por el usuario",
127
+ confirmDestructiveTitle: "Acci\xF3n destructiva",
128
+ confirmTitle: "Confirmaci\xF3n necesaria",
129
+ confirmMessage: '\xBFSeguro que quieres "{action}"?',
130
+ confirmDestructiveApprove: "S\xED, ejecutar",
131
+ confirmApprove: "Confirmar",
132
+ confirmCancel: "Cancelar",
133
+ confirmThisAction: "esta acci\xF3n",
134
+ confirmExpired: "Esta confirmaci\xF3n expir\xF3. Pide de nuevo al asistente para continuar.",
135
+ unknownAction: "Acci\xF3n desconocida",
136
+ somethingWentWrong: "Algo sali\xF3 mal",
137
+ openChatA11y: "Abrir chat del asistente",
138
+ closeChatA11y: "Cerrar chat del asistente",
139
+ stopGeneratingA11y: "Detener generaci\xF3n",
140
+ talkToHumanA11y: "Hablar con un agente humano",
141
+ humanAgentLabel: "Agente",
142
+ escalationRequested: "\xA1Listo! Llam\xE9 a un agente humano. Mientras tanto puedes seguir usando la app con normalidad.",
143
+ escalationConnected: "Un agente humano se uni\xF3 a la conversaci\xF3n.",
144
+ escalationResolved: "La atenci\xF3n humana termin\xF3. Vuelvo a ayudarte por aqu\xED.",
145
+ escalationOffer: "Si prefieres, puedo llamar a una persona para ayudarte.",
146
+ escalationOfferChip: "Hablar con un agente",
147
+ escalationPendingBanner: "Esperando a un agente...",
148
+ escalationActiveBanner: "Est\xE1s hablando con un agente humano",
149
+ pillWorking: "Trabajando...",
150
+ pillExpandA11y: "Abrir la conversaci\xF3n del asistente",
151
+ pillStopA11y: "Detener el asistente",
152
+ missionPause: "Pausar",
153
+ missionResume: "Reanudar tarea",
154
+ missionCancel: "Cancelar tarea",
155
+ missionPaused: "Pausada",
156
+ pillDone: "Listo \u2014 toca para ver",
157
+ a11yAwaitingApproval: "El asistente necesita tu confirmaci\xF3n para continuar.",
158
+ a11yTurnDone: "El asistente termin\xF3.",
159
+ micStartA11y: "Mantener para hablar",
160
+ micStopA11y: "Soltar para enviar",
161
+ micListening: "Escuchando..."
162
+ };
163
+
164
+ // src/i18n/bundles/fr.ts
165
+ var fr = {
166
+ chatTitle: "Assistant Appilots",
167
+ clearButton: "Effacer",
168
+ inputPlaceholder: "\xC9crivez un message\u2026",
169
+ emptyTitle: "Comment puis-je vous aider ?",
170
+ emptySubtitle: "Je navigue, remplis les formulaires et effectue des actions dans cette application.",
171
+ thinking: "R\xE9flexion en cours\u2026",
172
+ statusAnalyzing: "Lecture de l\u2019\xE9cran\u2026",
173
+ statusWaitingApp: "En attente de l\u2019application\u2026",
174
+ statusAdjusting: "Ajustement du plan\u2026",
175
+ stillWorking: "Toujours en cours\u2026 {seconds} s",
176
+ poweredBy: "Propuls\xE9 par Appilots",
177
+ approve: "Approuver",
178
+ reject: "Refuser",
179
+ cancelledByUser: "Annul\xE9 par l\u2019utilisateur",
180
+ confirmDestructiveTitle: "Action destructive",
181
+ confirmTitle: "Confirmation requise",
182
+ confirmMessage: "Voulez-vous vraiment \xAB {action} \xBB ?",
183
+ confirmDestructiveApprove: "Oui, ex\xE9cuter",
184
+ confirmApprove: "Confirmer",
185
+ confirmCancel: "Annuler",
186
+ confirmThisAction: "cette action",
187
+ confirmExpired: "Cette confirmation a expir\xE9. Redemandez \xE0 l\u2019assistant pour continuer.",
188
+ unknownAction: "Action inconnue",
189
+ somethingWentWrong: "Une erreur est survenue",
190
+ openChatA11y: "Ouvrir la conversation avec l\u2019assistant",
191
+ closeChatA11y: "Fermer la conversation",
192
+ stopGeneratingA11y: "Arr\xEAter la g\xE9n\xE9ration",
193
+ talkToHumanA11y: "Parler \xE0 un conseiller",
194
+ humanAgentLabel: "Conseiller",
195
+ escalationRequested: "J\u2019ai contact\xE9 un conseiller. Vous pouvez continuer \xE0 utiliser l\u2019application en attendant.",
196
+ escalationConnected: "Un conseiller a rejoint la conversation.",
197
+ escalationResolved: "L\u2019intervention du conseiller est termin\xE9e. Je peux \xE0 nouveau vous aider.",
198
+ escalationOffer: "Si vous pr\xE9f\xE9rez, je peux faire appel \xE0 un conseiller.",
199
+ escalationOfferChip: "Parler \xE0 un conseiller",
200
+ escalationPendingBanner: "En attente d\u2019un conseiller\u2026",
201
+ escalationActiveBanner: "Vous discutez avec un conseiller",
202
+ pillWorking: "En cours\u2026",
203
+ pillExpandA11y: "Ouvrir la conversation avec l\u2019assistant",
204
+ pillStopA11y: "Arr\xEAter l\u2019assistant",
205
+ missionPause: "Mettre en pause",
206
+ missionResume: "Reprendre la t\xE2che",
207
+ missionCancel: "Annuler la t\xE2che",
208
+ missionPaused: "En pause",
209
+ pillDone: "Termin\xE9 \u2014 touchez pour lire",
210
+ a11yAwaitingApproval: "L\u2019assistant a besoin de votre confirmation pour continuer.",
211
+ a11yTurnDone: "L\u2019assistant a termin\xE9.",
212
+ micStartA11y: "Maintenez pour parler",
213
+ micStopA11y: "Rel\xE2chez pour envoyer",
214
+ micListening: "\xC9coute en cours\u2026"
215
+ };
216
+
217
+ // src/i18n/I18nProvider.tsx
218
+ var BUNDLES = {
219
+ "pt-BR": ptBR,
220
+ en,
221
+ es,
222
+ fr
223
+ };
224
+ var DEFAULT_LOCALE = "en";
225
+ function detectDeviceLocale() {
226
+ try {
227
+ let raw;
228
+ if (Platform.OS === "ios") {
229
+ const manager = NativeModules?.SettingsManager;
230
+ const settings = manager?.getConstants?.()?.settings ?? manager?.settings;
231
+ const languages = Array.isArray(settings?.AppleLanguages) ? settings.AppleLanguages : [];
232
+ for (const language of languages) {
233
+ const locale = normalizeAppilotsLocale(language);
234
+ if (locale) return locale;
235
+ }
236
+ raw = settings?.AppleLocale;
237
+ } else if (Platform.OS === "android") {
238
+ const manager = NativeModules?.I18nManager;
239
+ raw = manager?.getConstants?.()?.localeIdentifier ?? manager?.localeIdentifier;
240
+ }
241
+ if (!raw || typeof raw !== "string") return null;
242
+ return normalizeAppilotsLocale(raw) ?? null;
243
+ } catch {
244
+ return null;
245
+ }
246
+ }
247
+ function resolveLocale(prop) {
248
+ if (prop && BUNDLES[prop]) return prop;
249
+ const detected = detectDeviceLocale();
250
+ if (detected && BUNDLES[detected]) return detected;
251
+ return DEFAULT_LOCALE;
252
+ }
253
+ var I18nContext = createContext(null);
254
+ function AppilotsI18nProvider({ locale, overrides, children }) {
255
+ const value = useMemo(() => {
256
+ const resolved = resolveLocale(locale ?? null);
257
+ const merged = overrides ? { ...BUNDLES[resolved], ...overrides } : BUNDLES[resolved];
258
+ return {
259
+ locale: resolved,
260
+ strings: merged,
261
+ t: (key) => merged[key]
262
+ };
263
+ }, [locale, overrides]);
264
+ return /* @__PURE__ */ React.createElement(I18nContext.Provider, { value }, children);
265
+ }
266
+ function useAppilotsI18n() {
267
+ const ctx = useContext(I18nContext);
268
+ if (ctx) return ctx;
269
+ return {
270
+ locale: DEFAULT_LOCALE,
271
+ strings: BUNDLES[DEFAULT_LOCALE],
272
+ t: (key) => BUNDLES[DEFAULT_LOCALE][key]
273
+ };
274
+ }
4
275
 
5
276
  // src/introspection/captureSnapshot.ts
6
277
  function normalizeSliderKey(value) {
@@ -74,20 +345,35 @@ function mergeRegisteredLists(snapshot) {
74
345
  }
75
346
  for (const entry of registered) {
76
347
  if (entry.kind === "scroll") continue;
348
+ const existing = byId.get(entry.id);
349
+ if (!existing && !(entry.screen && entry.screen === snapshot.route)) continue;
77
350
  const metrics = entry.getScrollMetrics?.();
78
351
  const scrollFacts = metrics ? {
79
352
  scrollOffsetY: Math.max(0, Math.round(metrics.offset)),
80
353
  canScrollUp: metrics.offset > 8,
81
354
  canScrollDown: metrics.offset + metrics.visibleLength < metrics.contentLength - 8
82
355
  } : void 0;
83
- const existing = byId.get(entry.id);
84
356
  if (existing) {
85
357
  existing.itemCount = existing.itemCount ?? entry.itemCount;
358
+ existing.totalItemCount = existing.totalItemCount ?? entry.totalItemCount;
86
359
  existing.refreshing = existing.refreshing ?? entry.refreshing;
87
360
  existing.empty = existing.empty ?? entry.empty;
88
361
  existing.label = existing.label ?? entry.label;
89
362
  existing.source = existing.source ?? "auto-tracked";
90
363
  existing.visibleItemCount = existing.items.length;
364
+ const viewport = entry.measureViewport?.();
365
+ if (viewport && viewport.width > 0 && viewport.height > 0) {
366
+ let measured = 0;
367
+ let inViewport = 0;
368
+ for (const item of existing.items) {
369
+ const rect = rectForNode(item);
370
+ if (!rect) continue;
371
+ measured++;
372
+ 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;
373
+ if (item.onScreen) inViewport++;
374
+ }
375
+ if (measured === existing.items.length) existing.viewportItemCount = inViewport;
376
+ }
91
377
  if (entry.dataPreview && entry.dataPreview.length > 0) {
92
378
  existing.dataPreview = entry.dataPreview;
93
379
  }
@@ -104,6 +390,7 @@ function mergeRegisteredLists(snapshot) {
104
390
  containerType: entry.component,
105
391
  source: "registry",
106
392
  itemCount: entry.itemCount,
393
+ totalItemCount: entry.totalItemCount,
107
394
  visibleItemCount: 0,
108
395
  refreshing: entry.refreshing,
109
396
  empty: entry.empty,
@@ -169,6 +456,43 @@ function recordBlindWalk() {
169
456
  if (totalDetectorHits(walk.detectors) > 0) return;
170
457
  recordIntrospectionFailure("walk-recognized-nothing", null);
171
458
  }
459
+ function mergeRegisteredFieldValues(snapshot) {
460
+ const present = new Set(snapshot.inputs.map((i) => normalizeFieldKey(i.id)));
461
+ const onScreenIds = [
462
+ ...snapshot.buttons.map((b) => (b.id ?? "").toLowerCase()),
463
+ ...snapshot.toggles.map((t) => (t.id ?? "").toLowerCase())
464
+ ].filter(Boolean);
465
+ const onScreenLabels = new Set(
466
+ [...snapshot.buttons.map((b) => b.label), ...snapshot.texts].filter((l) => !!l).map((l) => l.toLowerCase())
467
+ );
468
+ for (const { id, kind, screen, label } of componentRegistry.snapshot()) {
469
+ if (kind !== "field") continue;
470
+ const key = normalizeFieldKey(id);
471
+ if (!key || present.has(key)) continue;
472
+ const entry = componentRegistry.getField(id);
473
+ if (!entry) continue;
474
+ const fieldType = entry.fieldType ?? "";
475
+ if (fieldType === "text" || fieldType === "") continue;
476
+ const declaresRoute = typeof screen === "string" && screen === snapshot.route;
477
+ if (screen && !declaresRoute && !getActiveRouteNames().includes(screen)) continue;
478
+ const idEvidence = onScreenIds.some((candidate) => candidate.split(/[^a-z0-9]+/).includes(key));
479
+ const labelEvidence = !!entry.label && onScreenLabels.has(entry.label.toLowerCase());
480
+ if (!declaresRoute && !idEvidence && !labelEvidence) continue;
481
+ let value;
482
+ try {
483
+ value = String(entry.getValue() ?? "");
484
+ } catch {
485
+ continue;
486
+ }
487
+ snapshot.inputs.push({
488
+ id,
489
+ label: entry.label ?? label,
490
+ value,
491
+ type: fieldType
492
+ });
493
+ present.add(key);
494
+ }
495
+ }
172
496
  function captureSnapshot() {
173
497
  const root = getFiberRoot();
174
498
  if (!root) {
@@ -201,6 +525,7 @@ function captureSnapshot() {
201
525
  mergeNativeDialog(snapshot);
202
526
  reconcileSliders(snapshot);
203
527
  canonicalizeRegisteredFields(snapshot);
528
+ mergeRegisteredFieldValues(snapshot);
204
529
  clampSnapshotToWireLimits(snapshot);
205
530
  const entries = deriveInteractionElements(snapshot, { rectOf: rectForNode });
206
531
  const rectById = new Map(
@@ -303,6 +628,11 @@ function flipSwitchFiber(fiber, next) {
303
628
  return { ok: true, via: handler.via };
304
629
  }
305
630
 
631
+ // src/introspection/positionalIdentity.ts
632
+ function isPositionalControlId(value) {
633
+ return /^(?:list-\d+-item-\d+-)?(?:btn|input|slider|toggle)-\d+$/i.test(value.trim());
634
+ }
635
+
306
636
  // src/introspection/findInteractive.ts
307
637
  function getTypeName2(type) {
308
638
  if (!type) return null;
@@ -387,9 +717,17 @@ function shouldSkipSubtree2(fiber) {
387
717
  }
388
718
  return false;
389
719
  }
720
+ function isSecureInput(props) {
721
+ return !!props.secureTextEntry || isAppilotsSensitiveMarked(props);
722
+ }
390
723
  var normalize = foldForCompare;
391
724
  function scoreMatchDetail(fiber, queryNorm, queryRaw) {
392
725
  const props = fiber.memoizedProps ?? {};
726
+ if (isSourceControlId(queryRaw)) {
727
+ const matches = sourceControlId(fiber) === queryRaw;
728
+ const disabled = props.disabled === true || props.accessibilityState?.disabled === true;
729
+ return { score: matches ? disabled ? 60 : 120 : 0, named: matches, disabled };
730
+ }
393
731
  const candidates = [];
394
732
  if (typeof props.testID === "string") candidates.push({ val: props.testID, weight: 100 });
395
733
  if (typeof props.accessibilityLabel === "string")
@@ -403,16 +741,22 @@ function scoreMatchDetail(fiber, queryNorm, queryRaw) {
403
741
  if (childText) candidates.push({ val: childText, weight: 80 });
404
742
  }
405
743
  let best = 0;
744
+ let exact = false;
406
745
  for (const { val, weight } of candidates) {
407
746
  const vNorm = normalize(val);
408
747
  if (!vNorm) continue;
409
- if (val === queryRaw) best = Math.max(best, weight + 20);
410
- else if (vNorm === queryNorm) best = Math.max(best, weight + 10);
411
- else if (queryNorm && (vNorm.includes(queryNorm) || queryNorm.includes(vNorm)))
748
+ if (val === queryRaw) {
749
+ best = Math.max(best, weight + 20);
750
+ exact = true;
751
+ } else if (vNorm === queryNorm) {
752
+ best = Math.max(best, weight + 10);
753
+ exact = true;
754
+ } else if (!isPositionalControlId(queryRaw) && queryNorm && (vNorm.includes(queryNorm) || queryNorm.includes(vNorm)))
412
755
  best = Math.max(best, weight - 20);
413
756
  }
757
+ if (!exact && isSecureInput(props)) best = 0;
414
758
  const named = best >= IDENTITY_MATCH_SCORE;
415
- const disabledProp = props.disabled === true;
759
+ const disabledProp = props.disabled === true || props.editable === false || props.readOnly === true;
416
760
  const disabledA11y = typeof props.accessibilityState === "object" && props.accessibilityState !== null && props.accessibilityState.disabled === true;
417
761
  if (best > 0 && disabledProp) {
418
762
  best = Math.max(1, best - 60);
@@ -422,12 +766,18 @@ function scoreMatchDetail(fiber, queryNorm, queryRaw) {
422
766
  }
423
767
  return { score: best, named, disabled: disabledProp || disabledA11y };
424
768
  }
769
+ var inputIdentities = /* @__PURE__ */ new WeakMap();
425
770
  function wrapTextInput(fiber) {
771
+ const identity = inputIdentities.get(fiber) ?? (fiber.alternate && inputIdentities.get(fiber.alternate)) ?? {};
772
+ inputIdentities.set(fiber, identity);
773
+ if (fiber.alternate) inputIdentities.set(fiber.alternate, identity);
426
774
  const getProps = () => fiber.memoizedProps ?? {};
427
775
  return {
428
776
  kind: "field",
777
+ controlIdentity: identity,
429
778
  label: getProps().accessibilityLabel ?? getProps().label ?? getProps().placeholder ?? getProps().testID,
430
779
  getValue: () => getProps().value,
780
+ finishEditing: () => endTextInputEditing(getProps()),
431
781
  setValue: (value) => {
432
782
  const { onChangeText, onChange } = getProps();
433
783
  if (typeof onChangeText === "function") {
@@ -508,10 +858,10 @@ function describeCandidate(fiber, kind) {
508
858
  const props = fiber?.memoizedProps ?? {};
509
859
  return props.testID ?? props.accessibilityLabel ?? props.placeholder ?? (kind === "target" ? findChildText(fiber) : void 0);
510
860
  }
511
- function isDescendantOf(fiber, ancestor) {
861
+ function isDescendantOf(fiber, ancestor, parents) {
512
862
  if (!fiber || !ancestor) return false;
513
- let node = fiber.return;
514
- for (let depth = 0; node && depth < 200; depth++, node = node.return) {
863
+ let node = parents.get(fiber);
864
+ for (let depth = 0; node && depth < 200; depth++, node = parents.get(node)) {
515
865
  if (node === ancestor) return true;
516
866
  }
517
867
  return false;
@@ -532,13 +882,15 @@ function findInteractiveByIdentifier(query, options = {}) {
532
882
  let bestDisabled = false;
533
883
  let tied = [];
534
884
  const visited = /* @__PURE__ */ new WeakSet();
885
+ const parents = /* @__PURE__ */ new WeakMap();
535
886
  const stack = [];
536
- if (root.child) stack.push(root.child);
887
+ if (root.child) stack.push({ fiber: root.child, parent: root });
537
888
  while (stack.length > 0) {
538
- const fiber = stack.pop();
889
+ const { fiber, parent } = stack.pop();
539
890
  if (!fiber || visited.has(fiber)) continue;
540
891
  visited.add(fiber);
541
- if (fiber.sibling) stack.push(fiber.sibling);
892
+ parents.set(fiber, parent);
893
+ if (fiber.sibling) stack.push({ fiber: fiber.sibling, parent });
542
894
  if (shouldSkipSubtree2(fiber)) continue;
543
895
  const props = fiber.memoizedProps ?? {};
544
896
  let kind = null;
@@ -552,7 +904,7 @@ function findInteractiveByIdentifier(query, options = {}) {
552
904
  if (score > bestScore) tied = [{ fiber, kind }];
553
905
  else if (score === bestScore) tied.push({ fiber, kind });
554
906
  }
555
- const beatsBest = score > bestScore || score > 0 && score === bestScore && isDescendantOf(fiber, bestFiber);
907
+ const beatsBest = score > bestScore || score > 0 && score === bestScore && isDescendantOf(fiber, bestFiber, parents);
556
908
  if (beatsBest) {
557
909
  bestScore = score;
558
910
  bestFiber = fiber;
@@ -561,11 +913,15 @@ function findInteractiveByIdentifier(query, options = {}) {
561
913
  bestId = describeCandidate(fiber, kind);
562
914
  if (kind === "field") bestHandle = wrapTextInput(fiber);
563
915
  else if (kind === "toggle") bestHandle = wrapSwitch(fiber);
564
- else bestHandle = wrapTouchable(fiber, findChildText(fiber));
916
+ else if (isSourceControlId(queryRaw)) {
917
+ const bound = sourceControlTarget(fiber);
918
+ if (!bound || bound.disabled) bestHandle = null;
919
+ else bestHandle = wrapTouchable(bound.fiber, findChildText(fiber));
920
+ } else bestHandle = wrapTouchable(fiber, findChildText(fiber));
565
921
  }
566
922
  }
567
923
  if (kind === "toggle") continue;
568
- if (fiber.child) stack.push(fiber.child);
924
+ if (fiber.child) stack.push({ fiber: fiber.child, parent: fiber });
569
925
  }
570
926
  if (bestHandle && bestScore < (options.minScore ?? 0)) {
571
927
  appilotsDebugLog(
@@ -576,12 +932,14 @@ function findInteractiveByIdentifier(query, options = {}) {
576
932
  if (bestHandle) {
577
933
  const independent = tied.filter(
578
934
  (candidate) => !tied.some(
579
- (other) => other.fiber !== candidate.fiber && isDescendantOf(other.fiber, candidate.fiber)
935
+ (other) => other.fiber !== candidate.fiber && isDescendantOf(other.fiber, candidate.fiber, parents)
580
936
  )
581
937
  );
582
938
  bestHandle.matchCount = independent.length;
583
939
  bestHandle.named = bestNamed;
584
940
  bestHandle.disabled = bestDisabled;
941
+ const measured = bestFiber;
942
+ bestHandle.measure = () => rectOfFiber(measured);
585
943
  const candidates = independent.map(({ fiber, kind }) => describeCandidate(fiber, kind)).filter((id) => typeof id === "string" && id.length > 0).slice(0, 8);
586
944
  if (candidates.length > 0) bestHandle.matchCandidates = candidates;
587
945
  appilotsDebugLog(
@@ -593,6 +951,127 @@ function findInteractiveByIdentifier(query, options = {}) {
593
951
  return bestHandle;
594
952
  }
595
953
 
954
+ // src/introspection/spotlightTarget.ts
955
+ var listeners = /* @__PURE__ */ new Set();
956
+ var announcementListeners = /* @__PURE__ */ new Set();
957
+ var current = null;
958
+ var seq = 0;
959
+ var expiry = null;
960
+ var SPOTLIGHT_TTL_MS = 1200;
961
+ var mode = "off";
962
+ function setSpotlightMode(next) {
963
+ mode = next;
964
+ }
965
+ function getSpotlightMode() {
966
+ return mode;
967
+ }
968
+ function emit() {
969
+ for (const listener of listeners) listener(current);
970
+ }
971
+ function setSpotlightTarget(rect, label, verb) {
972
+ if (label && verb) for (const listener of announcementListeners) listener(label, verb);
973
+ if (expiry) {
974
+ clearTimeout(expiry);
975
+ expiry = null;
976
+ }
977
+ if (!rect) {
978
+ appilotsDebugLog(
979
+ `setSpotlightTarget: cleared${label ? ` (was about to mark "${label}")` : ""}`
980
+ );
981
+ if (current === null) return;
982
+ current = null;
983
+ emit();
984
+ return;
985
+ }
986
+ current = { rect, label, seq: ++seq };
987
+ appilotsDebugLog(
988
+ `setSpotlightTarget: seq=${current.seq} label="${label ?? ""}" rect=${Math.round(rect.x)},${Math.round(rect.y)} ${Math.round(rect.width)}x${Math.round(rect.height)}`
989
+ );
990
+ emit();
991
+ expiry = setTimeout(() => {
992
+ expiry = null;
993
+ current = null;
994
+ emit();
995
+ }, SPOTLIGHT_TTL_MS);
996
+ }
997
+ function getSpotlightTarget() {
998
+ return current;
999
+ }
1000
+ function subscribeSpotlight(listener) {
1001
+ listeners.add(listener);
1002
+ return () => {
1003
+ listeners.delete(listener);
1004
+ };
1005
+ }
1006
+ function subscribeSpotlightAnnouncement(listener) {
1007
+ announcementListeners.add(listener);
1008
+ return () => {
1009
+ announcementListeners.delete(listener);
1010
+ };
1011
+ }
1012
+
1013
+ // src/introspection/scrollIntoView.ts
1014
+ var REVEAL_MARGIN = 24;
1015
+ function offsetToReveal({ rect, viewport, metrics }) {
1016
+ const maxOffset = Math.max(0, metrics.contentLength - metrics.visibleLength);
1017
+ const viewTop = viewport.top + REVEAL_MARGIN;
1018
+ const viewBottom = viewport.top + viewport.height - REVEAL_MARGIN;
1019
+ const tallerThanViewport = rect.height > viewport.height - REVEAL_MARGIN * 2;
1020
+ let delta;
1021
+ if (tallerThanViewport) {
1022
+ if (rect.y >= viewTop && rect.y <= viewBottom) return null;
1023
+ delta = rect.y - viewTop;
1024
+ } else if (rect.y < viewTop) {
1025
+ delta = rect.y - viewTop;
1026
+ } else if (rect.y + rect.height > viewBottom) {
1027
+ delta = rect.y + rect.height - viewBottom;
1028
+ } else {
1029
+ return null;
1030
+ }
1031
+ const target = clamp(metrics.offset + delta, 0, maxOffset);
1032
+ if (Math.abs(target - metrics.offset) < 1) return null;
1033
+ return target;
1034
+ }
1035
+ function clamp(value, min, max) {
1036
+ return Math.min(max, Math.max(min, value));
1037
+ }
1038
+
1039
+ // src/introspection/revealTarget.ts
1040
+ function revealTarget(rect) {
1041
+ const host = getGeometryHost();
1042
+ if (!host) return { rect, scrolled: false };
1043
+ if (isRectOnScreen(rect, host.window)) return { rect, scrolled: false };
1044
+ const scrollable = resolveScrollable();
1045
+ if (!scrollable) return { rect, scrolled: false };
1046
+ const metrics = scrollable.getScrollMetrics?.();
1047
+ if (!metrics) return { rect, scrolled: false };
1048
+ const box = scrollable.measureViewport?.();
1049
+ const viewport = box ? { top: box.y, height: box.height } : { top: 0, height: host.window.height };
1050
+ const target = offsetToReveal({ rect, viewport, metrics });
1051
+ if (target === null) return { rect, scrolled: false };
1052
+ const moved = scrollable.scrollToOffset?.(target) ?? false;
1053
+ if (!moved) {
1054
+ appilotsDebugLog(`revealTarget: "${scrollable.id}" refused scrollToOffset(${target})`);
1055
+ return { rect, scrolled: false };
1056
+ }
1057
+ const delta = target - metrics.offset;
1058
+ appilotsDebugLog(
1059
+ `revealTarget: scrolled "${scrollable.id}" by ${Math.round(delta)}px (${Math.round(metrics.offset)} \u2192 ${Math.round(target)})`
1060
+ );
1061
+ return { rect: { ...rect, y: rect.y - delta }, scrolled: true };
1062
+ }
1063
+ function resolveScrollable() {
1064
+ const screen = getCurrentScreen();
1065
+ const candidates = listRegistry.snapshot().filter((entry) => entry.scrollToOffset && entry.getScrollMetrics).filter((entry) => !screen || !entry.screen || entry.screen === screen);
1066
+ if (candidates.length === 1) return candidates[0];
1067
+ if (candidates.length > 1) {
1068
+ appilotsDebugLog(
1069
+ `revealTarget: ${candidates.length} scrollables on "${screen ?? "?"}" \u2014 cannot tell which holds the target, so not scrolling.`
1070
+ );
1071
+ }
1072
+ return null;
1073
+ }
1074
+
596
1075
  // src/executor/handlers/navigateHandler.ts
597
1076
  var _reportedMissingNavRef = false;
598
1077
  function reportMissingNavigationRef() {
@@ -635,7 +1114,18 @@ function screenExistsAnywhere(state, screenName) {
635
1114
  return false;
636
1115
  }
637
1116
  function safeNavigate(nav, screenName, params, path) {
1117
+ const state = nav.getState?.();
1118
+ const rootState = nav.getRootState?.() ?? state;
638
1119
  if (path && path.length > 0) {
1120
+ if (Array.isArray(rootState?.routeNames) && !screenExistsAnywhere(rootState, path[0]) && !state?.routeNames?.includes(path[0])) {
1121
+ if (state?.routeNames?.includes(screenName)) {
1122
+ nav.navigate(screenName, params);
1123
+ return;
1124
+ }
1125
+ throw new Error(
1126
+ `Route "${path[0]}" is not registered in any navigator. Cannot resolve the supplied path to "${screenName}".`
1127
+ );
1128
+ }
639
1129
  const { root, nestedParams } = buildNestedParams(path, screenName, params);
640
1130
  appilotsDebugLog(
641
1131
  `safeNavigate: using path-based navigation \u2014 root="${root}", nested=${JSON.stringify(nestedParams)}`
@@ -643,12 +1133,10 @@ function safeNavigate(nav, screenName, params, path) {
643
1133
  nav.navigate(root, nestedParams);
644
1134
  return;
645
1135
  }
646
- const state = nav.getState?.();
647
1136
  if (state?.routeNames?.includes(screenName)) {
648
1137
  nav.navigate(screenName, params);
649
1138
  return;
650
1139
  }
651
- const rootState = nav.getRootState?.() ?? state;
652
1140
  if (!screenExistsAnywhere(rootState, screenName)) {
653
1141
  throw new Error(
654
1142
  `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 +1158,8 @@ function navigateHandler(payload, context) {
670
1158
  if (!permissions.canNavigate) {
671
1159
  return {
672
1160
  success: false,
673
- error: "Navigation is not permitted",
674
- diagnose: { category: "unknown", screen: payload.screenName }
1161
+ error: permissionDeniedMessage("Navigation", "canNavigate"),
1162
+ diagnose: { category: "unknown", screen: payload.screenName, recoverable: false }
675
1163
  };
676
1164
  }
677
1165
  const isGoBack = payload.navigationAction === "goBack";
@@ -1028,8 +1516,10 @@ function pressResolvedListItem(list, itemFiber, listIndex, itemIndex) {
1028
1516
  (candidate) => candidate !== touchable && containsFiber(touchable, candidate)
1029
1517
  );
1030
1518
  if (primaryWrapsNestedTargets) {
1519
+ const outer = describeTouchable(touchable) ?? "?";
1520
+ const nested = safeIdentifiableTouchables.filter((candidate) => candidate !== touchable && containsFiber(touchable, candidate)).map((candidate) => describeTouchable(candidate) ?? "?").slice(0, 6);
1031
1521
  appilotsDebugLog(
1032
- `listItemDispatch: item "list-${listIndex}-item-${itemIndex}" has nested pressables; pressing outer row target`
1522
+ `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
1523
  );
1034
1524
  } else {
1035
1525
  const candidates = safeIdentifiableTouchables.map(describeTouchable).filter((d) => !!d).slice(0, 8);
@@ -1096,10 +1586,12 @@ function scoreItemCandidate(item, zeroBasedIndex, locator) {
1096
1586
  if (locator.itemKey) {
1097
1587
  if (itemKey === locator.itemKey) score += 1e3;
1098
1588
  else if (itemKey && normalize2(itemKey) === normalize2(locator.itemKey)) score += 950;
1589
+ else return 0;
1099
1590
  }
1100
1591
  if (locator.reactKey) {
1101
1592
  if (reactKey === locator.reactKey) score += 900;
1102
1593
  else if (reactKey && normalize2(reactKey) === normalize2(locator.reactKey)) score += 850;
1594
+ else if (!locator.itemKey) return 0;
1103
1595
  }
1104
1596
  if (locator.itemIndex && zeroBasedIndex === locator.itemIndex - 1) score += 750;
1105
1597
  if (locator.itemIndex && markedIndex !== void 0 && (markedIndex === locator.itemIndex || markedIndex === locator.itemIndex - 1)) {
@@ -1169,7 +1661,7 @@ function resolveListItemByIdentity(locator) {
1169
1661
  scoredCandidates.sort((a, b) => b.score - a.score);
1170
1662
  const best = scoredCandidates[0];
1171
1663
  const tiedCandidates = scoredCandidates.filter((c) => c.score === best.score);
1172
- if (tiedCandidates.length > 1 && best.score < 750) {
1664
+ if (tiedCandidates.length > 1 && (best.score < 750 || Boolean(effective.itemKey || effective.reactKey))) {
1173
1665
  const candidates = tiedCandidates.map((c) => describeItemCandidate(c.listIndex, c.itemIndex - 1, c.item)).slice(0, 8);
1174
1666
  return {
1175
1667
  ok: false,
@@ -1237,11 +1729,11 @@ function toggleListItemByIdentity(locator, options) {
1237
1729
  }
1238
1730
  const target = switches[0];
1239
1731
  const props = target?.memoizedProps ?? {};
1240
- const current = switchValue(props);
1241
- if (options?.expected !== void 0 && current === options.expected) {
1242
- return { ok: true, containerType: list.containerType, value: current, noop: true };
1732
+ const current2 = switchValue(props);
1733
+ if (options?.expected !== void 0 && current2 === options.expected) {
1734
+ return { ok: true, containerType: list.containerType, value: current2, noop: true };
1243
1735
  }
1244
- const flipped = flipSwitchFiber(target, !current);
1736
+ const flipped = flipSwitchFiber(target, !current2);
1245
1737
  if (!flipped.ok) {
1246
1738
  if (flipped.reason === "no-handler") {
1247
1739
  return {
@@ -1258,7 +1750,7 @@ function toggleListItemByIdentity(locator, options) {
1258
1750
  containerType: list.containerType
1259
1751
  };
1260
1752
  }
1261
- return { ok: true, containerType: list.containerType, value: !current };
1753
+ return { ok: true, containerType: list.containerType, value: !current2 };
1262
1754
  }
1263
1755
  function pressListItemByIdentity(locator) {
1264
1756
  const resolved = resolveListItemByIdentity(locator);
@@ -1453,6 +1945,13 @@ function dispatchFiberTarget(targetId, action) {
1453
1945
  diagnose: screenDiagnose(targetId)
1454
1946
  };
1455
1947
  }
1948
+ const measured = handle.measure?.() ?? null;
1949
+ const verb = action === "scroll" ? void 0 : action === "toggle" || action === "focus" ? action : "press";
1950
+ if (measured && getSpotlightMode() === "guided") {
1951
+ setSpotlightTarget(revealTarget(measured).rect, handle.label, verb);
1952
+ } else {
1953
+ setSpotlightTarget(measured, handle.label, verb);
1954
+ }
1456
1955
  try {
1457
1956
  if (isPressLike(action) && handle.kind === "target" && handle.press) {
1458
1957
  handle.press();
@@ -1503,6 +2002,13 @@ function dispatchLegacyTarget(targetId, registry, action) {
1503
2002
  };
1504
2003
  }
1505
2004
  function dispatchElement(element, registry, action) {
2005
+ if (element.dispatchable === false) {
2006
+ return {
2007
+ success: false,
2008
+ error: `Element "${element.id}" is observation-only and has no exact live control binding.`,
2009
+ diagnose: { ...screenDiagnose(element.id, "ambiguous-target"), recoverable: false }
2010
+ };
2011
+ }
1506
2012
  if (element.disabled === true) {
1507
2013
  return {
1508
2014
  success: false,
@@ -1521,6 +2027,13 @@ function dispatchElement(element, registry, action) {
1521
2027
  texts: element.texts
1522
2028
  });
1523
2029
  if (pressed.ok) return { success: true };
2030
+ if (!pressed.refusal && (element.listContext.itemKey || element.listContext.reactKey)) {
2031
+ return {
2032
+ success: false,
2033
+ error: pressed.error ?? `The observed list entity "${element.label ?? element.id}" could not be resolved. Reobserve; do not substitute its old position.`,
2034
+ diagnose: { ...screenDiagnose(element.id, "ambiguous-target"), recoverable: true }
2035
+ };
2036
+ }
1524
2037
  if (pressed.refusal) {
1525
2038
  return {
1526
2039
  success: false,
@@ -1704,19 +2217,9 @@ function collectKnownFormFields(registry) {
1704
2217
  }
1705
2218
  return registryFields;
1706
2219
  }
1707
- function isMisplacedFieldId(fieldId, knownIds) {
2220
+ function isMisplacedFieldId(fieldId) {
1708
2221
  const trimmed = fieldId.trim();
1709
- if (!trimmed) return true;
1710
- if (LIST_ORDINAL_RE.test(trimmed)) return true;
1711
- if (/^\d+$/.test(trimmed)) return true;
1712
- const normalized = normalize4(trimmed);
1713
- if (knownIds.has(normalized)) return false;
1714
- for (const known of knownIds) {
1715
- if (known.length >= 4 && (known.includes(normalized) || normalized.includes(known))) {
1716
- return false;
1717
- }
1718
- }
1719
- return true;
2222
+ return !trimmed || LIST_ORDINAL_RE.test(trimmed) || /^\d+$/.test(trimmed);
1720
2223
  }
1721
2224
  function valueMatchesOption(field, rawValue) {
1722
2225
  const value = normalize4(String(rawValue ?? ""));
@@ -1773,6 +2276,7 @@ function fieldTypeMatchesShape(field, shape) {
1773
2276
  function fieldAcceptsFreeText(field, shape) {
1774
2277
  const type = normalize4(field.type ?? "text");
1775
2278
  if (CHOICE_FIELD_TYPES.has(type)) return false;
2279
+ if (type === "password") return false;
1776
2280
  if (NUMERIC_FIELD_TYPES.has(type)) return shape === "number";
1777
2281
  if (DATE_FIELD_TYPES.has(type)) return shape === "date";
1778
2282
  if (EMAIL_FIELD_TYPES.has(type)) return shape === "email";
@@ -1793,12 +2297,11 @@ function inferFieldIdForValue(rawValue, fields, used) {
1793
2297
  function rewriteMisplacedFormFillFields(fields, registry) {
1794
2298
  const knownFields = collectKnownFormFields(registry);
1795
2299
  if (knownFields.length === 0) return fields;
1796
- const knownIds = new Set(knownFields.map((field) => normalize4(field.id)));
1797
- const needsRewrite = fields.some((field) => isMisplacedFieldId(field.fieldId, knownIds));
2300
+ const needsRewrite = fields.some((field) => isMisplacedFieldId(field.fieldId));
1798
2301
  if (!needsRewrite) return fields;
1799
2302
  const used = /* @__PURE__ */ new Set();
1800
2303
  return fields.map((field) => {
1801
- if (!isMisplacedFieldId(field.fieldId, knownIds)) {
2304
+ if (!isMisplacedFieldId(field.fieldId)) {
1802
2305
  used.add(field.fieldId);
1803
2306
  return field;
1804
2307
  }
@@ -1838,12 +2341,18 @@ function normalizeText(value) {
1838
2341
  function containsWord(haystack, needle) {
1839
2342
  return ` ${haystack} `.includes(` ${needle} `);
1840
2343
  }
1841
- function readVisibleFormValidation(snap) {
2344
+ function readVisibleFormValidation(snap, beforeTexts) {
1842
2345
  if (!snap) return void 0;
2346
+ const invalid = snap.inputs?.find((input) => input.invalid === true);
2347
+ const hasSubmitControl = snap.buttons?.some(
2348
+ (button) => SUBMIT_CONTROL_RE.test(button.label ?? button.id ?? "")
2349
+ );
1843
2350
  for (const text of snap.texts ?? []) {
1844
- if (!text?.trim() || !FORM_VALIDATION_TEXT_RE2.test(text)) continue;
2351
+ if (!text?.trim() || !isVisibleValidationText(text, { invalid: !!invalid, hasSubmitControl, beforeTexts }))
2352
+ continue;
1845
2353
  return { fieldId: inferValidationFieldId(text, snap.inputs), message: text };
1846
2354
  }
2355
+ if (invalid) return { fieldId: invalid.id, message: "The app marks this field as invalid." };
1847
2356
  return void 0;
1848
2357
  }
1849
2358
  function readVisibleFormSuccess(snap) {
@@ -1856,15 +2365,18 @@ function submitAsyncStillPending(submitTargetId) {
1856
2365
  if (submitTargetId && probe.pressedFound && probe.pressedDisabled) return true;
1857
2366
  return false;
1858
2367
  }
1859
- function evaluatePostSubmitSnapshot(snap, activePath) {
1860
- const validation = readVisibleFormValidation(snap);
2368
+ function evaluatePostSubmitSnapshot(snap, activePath, beforeTexts) {
2369
+ const validation = readVisibleFormValidation(snap, beforeTexts);
1861
2370
  if (validation) {
1862
2371
  return { status: "failure", fieldId: validation.fieldId, message: validation.message };
1863
2372
  }
1864
2373
  const successText = readVisibleFormSuccess(snap);
1865
2374
  if (successText) return { status: "success", message: successText };
1866
2375
  if (!snapshotShowsOpenCreateForm(snap, activePath)) {
1867
- return { status: "success" };
2376
+ return {
2377
+ status: "unverified",
2378
+ message: "The form closed without a visible success receipt. Inspect the resulting record before claiming the goal is complete."
2379
+ };
1868
2380
  }
1869
2381
  return void 0;
1870
2382
  }
@@ -1880,7 +2392,7 @@ async function waitForPostSubmitOutcome(options) {
1880
2392
  }
1881
2393
  const snap2 = captureSnapshot();
1882
2394
  const activePath2 = getActiveRouteNames();
1883
- const outcome2 = evaluatePostSubmitSnapshot(snap2, activePath2);
2395
+ const outcome2 = evaluatePostSubmitSnapshot(snap2, activePath2, options?.beforeTexts);
1884
2396
  if (outcome2) return outcome2;
1885
2397
  await sleep(pollMs);
1886
2398
  }
@@ -1892,7 +2404,7 @@ async function waitForPostSubmitOutcome(options) {
1892
2404
  }
1893
2405
  const snap = captureSnapshot();
1894
2406
  const activePath = getActiveRouteNames();
1895
- const outcome = evaluatePostSubmitSnapshot(snap, activePath);
2407
+ const outcome = evaluatePostSubmitSnapshot(snap, activePath, options?.beforeTexts);
1896
2408
  if (outcome) return outcome;
1897
2409
  return {
1898
2410
  status: "failure",
@@ -1900,91 +2412,6 @@ async function waitForPostSubmitOutcome(options) {
1900
2412
  };
1901
2413
  }
1902
2414
 
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
2415
  // src/executor/handlers/uiInteractionHandler.ts
1989
2416
  function findSnapshotButton(targetId) {
1990
2417
  try {
@@ -2057,6 +2484,7 @@ function stripComponentPrefix(componentId) {
2057
2484
  return null;
2058
2485
  }
2059
2486
  function resolveComponentId(componentId, registry, action) {
2487
+ if (isSourceControlId(componentId)) return componentId;
2060
2488
  if (registry.getTarget(componentId) || registry.getToggle(componentId) || registry.getField(componentId) || registry.getSlider(componentId)) {
2061
2489
  return componentId;
2062
2490
  }
@@ -2070,6 +2498,7 @@ function resolveComponentId(componentId, registry, action) {
2070
2498
  return comp.id;
2071
2499
  }
2072
2500
  }
2501
+ if (isPositionalControlId(componentId)) return componentId;
2073
2502
  const currentScreen = getCurrentScreen();
2074
2503
  if (currentScreen) {
2075
2504
  const screenMeta = getScreenMetadata(currentScreen);
@@ -2315,11 +2744,14 @@ function uiInteractionHandler(payload, context) {
2315
2744
  if (!permissions.canInteractUI) {
2316
2745
  return {
2317
2746
  success: false,
2318
- error: "UI interaction is not permitted",
2319
- diagnose: buildUiDiagnose(
2320
- payload.componentId ?? payload.targetId ?? "",
2321
- "unknown"
2322
- )
2747
+ error: permissionDeniedMessage("UI interaction", "canInteractUI"),
2748
+ diagnose: {
2749
+ ...buildUiDiagnose(
2750
+ payload.componentId ?? payload.targetId ?? "",
2751
+ "unknown"
2752
+ ),
2753
+ recoverable: false
2754
+ }
2323
2755
  };
2324
2756
  }
2325
2757
  const { action } = payload;
@@ -2549,6 +2981,28 @@ function uiInteractionHandler(payload, context) {
2549
2981
  };
2550
2982
  }
2551
2983
 
2984
+ // src/executor/completeTextEdit.ts
2985
+ var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
2986
+ async function completeTextEdit(fieldId, expected, registered, origin, resolveRegistered) {
2987
+ await sleep2(80);
2988
+ const current2 = findInteractiveByIdentifier(fieldId, { kind: "field" });
2989
+ if (origin?.controlIdentity && current2?.controlIdentity !== origin.controlIdentity) return false;
2990
+ if (registered && resolveRegistered && resolveRegistered() !== registered) return false;
2991
+ if (registered?.finishEditing) registered.finishEditing();
2992
+ else if (current2 && (current2.matchCount ?? 1) === 1 && !current2.disabled)
2993
+ current2.finishEditing?.();
2994
+ const started = Date.now();
2995
+ do {
2996
+ await sleep2(80);
2997
+ const live = findInteractiveByIdentifier(fieldId, { kind: "field" });
2998
+ if (origin?.controlIdentity && live?.controlIdentity !== origin.controlIdentity) return false;
2999
+ if (registered && resolveRegistered && resolveRegistered() !== registered) return false;
3000
+ const value = registered ? registered.getValue() : live?.getValue?.();
3001
+ if (value !== void 0 && value !== null && String(value) === String(expected)) return true;
3002
+ } while (Date.now() - started < 480);
3003
+ return false;
3004
+ }
3005
+
2552
3006
  // src/executor/formSubmitResolve.ts
2553
3007
  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
3008
  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 +3149,8 @@ function selectValueApplied(fieldId, requestedValue, currentValue, registry) {
2695
3149
  function getMetadataField(fieldId, registry) {
2696
3150
  const screens = /* @__PURE__ */ new Set();
2697
3151
  for (const name of getActiveRouteNames()) screens.add(name);
2698
- const current = getCurrentScreen();
2699
- if (current) screens.add(current);
3152
+ const current2 = getCurrentScreen();
3153
+ if (current2) screens.add(current2);
2700
3154
  if (registry) {
2701
3155
  for (const comp of registry.snapshot()) {
2702
3156
  if (comp.kind === "field" && comp.screen) screens.add(comp.screen);
@@ -2737,7 +3191,7 @@ async function tryOpenSelectModal(fieldId, registry) {
2737
3191
  if (!target) continue;
2738
3192
  try {
2739
3193
  target.press();
2740
- await sleep2(180);
3194
+ await sleep3(180);
2741
3195
  return true;
2742
3196
  } catch {
2743
3197
  }
@@ -3016,6 +3470,10 @@ function findField(fieldId, registry) {
3016
3470
  return registry.getField(comp.id);
3017
3471
  }
3018
3472
  }
3473
+ if (findInteractiveByIdentifier(fieldId, { kind: "field", minScore: IDENTITY_MATCH_SCORE })) {
3474
+ appilotsDebugLog(`findField: "${fieldId}" names a live input \u2014 not guessing in the registry`);
3475
+ return void 0;
3476
+ }
3019
3477
  const currentScreenForTieBreak = getCurrentScreen();
3020
3478
  const needle = normalize3(fieldId);
3021
3479
  let bestComp;
@@ -3122,7 +3580,7 @@ function findSlider(fieldId, registry) {
3122
3580
  }
3123
3581
  return void 0;
3124
3582
  }
3125
- var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
3583
+ var sleep3 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
3126
3584
  async function formFillHandler(payload, context) {
3127
3585
  const { permissions } = context;
3128
3586
  const registry = context.registry ?? componentRegistry;
@@ -3131,8 +3589,8 @@ async function formFillHandler(payload, context) {
3131
3589
  if (!permissions.canFillForms) {
3132
3590
  return {
3133
3591
  success: false,
3134
- error: "Form filling is not permitted",
3135
- diagnose: { category: "unknown", screen: screenPath }
3592
+ error: permissionDeniedMessage("Form filling", "canFillForms"),
3593
+ diagnose: { category: "unknown", screen: screenPath, recoverable: false }
3136
3594
  };
3137
3595
  }
3138
3596
  if (!payload.fields || payload.fields.length === 0) {
@@ -3189,14 +3647,51 @@ async function formFillHandler(payload, context) {
3189
3647
  const fieldEntry = findField(fieldId, registry);
3190
3648
  if (fieldEntry) {
3191
3649
  try {
3650
+ const nativeField = findInteractiveByIdentifier(fieldId, { kind: "field" });
3651
+ if (nativeField?.disabled || (nativeField?.matchCount ?? 1) > 1) {
3652
+ fieldResults.push({
3653
+ fieldId,
3654
+ success: false,
3655
+ error: nativeField?.disabled ? "The field is disabled" : "The field identifier matches multiple controls",
3656
+ diagnose: {
3657
+ category: nativeField?.disabled ? "disabled" : "ambiguous-target",
3658
+ fieldId,
3659
+ screen: screenPath
3660
+ }
3661
+ });
3662
+ allSuccess = false;
3663
+ continue;
3664
+ }
3192
3665
  appilotsDebugLog(
3193
3666
  `formFillHandler: found field "${fieldId}" via registry, calling setValue(<${String(value).length} chars>)`
3194
3667
  );
3195
3668
  const beforeValue = effectiveFieldType === "date" ? fieldEntry.getValue?.() ?? "" : void 0;
3196
3669
  fieldEntry.focus?.();
3197
3670
  fieldEntry.setValue(String(value));
3671
+ if (effectiveFieldType === "text" || effectiveFieldType === "number") {
3672
+ if (!await completeTextEdit(
3673
+ fieldId,
3674
+ value,
3675
+ fieldEntry,
3676
+ nativeField,
3677
+ () => findField(fieldId, registry)
3678
+ )) {
3679
+ fieldResults.push({
3680
+ fieldId,
3681
+ success: false,
3682
+ error: "The field did not retain the requested value after editing ended. Inspect its current value before retrying.",
3683
+ diagnose: { category: "validation", fieldId, screen: screenPath }
3684
+ });
3685
+ allSuccess = false;
3686
+ continue;
3687
+ }
3688
+ }
3198
3689
  if (effectiveFieldType === "date") {
3199
- const afterValue = fieldEntry.getValue?.() ?? "";
3690
+ let afterValue = fieldEntry.getValue?.() ?? "";
3691
+ if (afterValue === beforeValue) {
3692
+ await sleep3(80);
3693
+ afterValue = fieldEntry.getValue?.() ?? "";
3694
+ }
3200
3695
  if (afterValue === beforeValue) {
3201
3696
  fieldResults.push({
3202
3697
  fieldId,
@@ -3216,7 +3711,7 @@ async function formFillHandler(payload, context) {
3216
3711
  if (effectiveFieldType === "select") {
3217
3712
  let after = fieldEntry.getValue?.() ?? "";
3218
3713
  if (!selectValueApplied(fieldId, value, after, registry)) {
3219
- await sleep2(80);
3714
+ await sleep3(80);
3220
3715
  after = fieldEntry.getValue?.() ?? "";
3221
3716
  }
3222
3717
  if (!selectValueApplied(fieldId, value, after, registry)) {
@@ -3337,7 +3832,7 @@ async function formFillHandler(payload, context) {
3337
3832
  if (!selectResult.success && selectedChoiceThisBatch) {
3338
3833
  const nextResult = pressContinuationButton(registry);
3339
3834
  if (nextResult.success) {
3340
- await sleep2(180);
3835
+ await sleep3(180);
3341
3836
  selectResult = await selectVisibleChoice(fieldId, value, registry, {
3342
3837
  allowFirstVisibleWhenNoCue: true
3343
3838
  });
@@ -3348,7 +3843,7 @@ async function formFillHandler(payload, context) {
3348
3843
  appilotsDebugLog(
3349
3844
  `formFillHandler: selected visible option for "${fieldId}"` + (selectResult.selectedLabel ? ` \u2192 "${selectResult.selectedLabel}"` : "")
3350
3845
  );
3351
- await sleep2(120);
3846
+ await sleep3(120);
3352
3847
  fieldResults.push({ fieldId, success: true });
3353
3848
  continue;
3354
3849
  }
@@ -3360,8 +3855,34 @@ async function formFillHandler(payload, context) {
3360
3855
  });
3361
3856
  if (handle?.setValue) {
3362
3857
  try {
3858
+ if (handle.disabled || (handle.matchCount ?? 1) > 1) {
3859
+ fieldResults.push({
3860
+ fieldId,
3861
+ success: false,
3862
+ error: handle.disabled ? "The field is disabled" : "The field identifier matches multiple controls",
3863
+ diagnose: {
3864
+ category: handle.disabled ? "disabled" : "ambiguous-target",
3865
+ fieldId,
3866
+ screen: screenPath,
3867
+ candidates: handle.matchCandidates
3868
+ }
3869
+ });
3870
+ allSuccess = false;
3871
+ continue;
3872
+ }
3873
+ setSpotlightTarget(handle.measure?.() ?? null, handle.label, "fill");
3363
3874
  handle.focus?.();
3364
3875
  handle.setValue(value);
3876
+ if (handle.kind === "field" && !await completeTextEdit(fieldId, value, void 0, handle)) {
3877
+ fieldResults.push({
3878
+ fieldId,
3879
+ success: false,
3880
+ error: "The field did not retain the requested value after editing ended. Inspect its current value before retrying.",
3881
+ diagnose: { category: "validation", fieldId, screen: screenPath }
3882
+ });
3883
+ allSuccess = false;
3884
+ continue;
3885
+ }
3365
3886
  appilotsDebugLog(
3366
3887
  `formFillHandler: fiber fallback succeeded for "${fieldId}" \u2192 label="${handle.label}"`
3367
3888
  );
@@ -3413,10 +3934,11 @@ async function formFillHandler(payload, context) {
3413
3934
  "formFillHandler: submitAfterFill=true requested but some fields failed; NOT submitting" + (failedIds.length > 0 ? ` (failed: ${failedIds.join(", ")})` : "")
3414
3935
  );
3415
3936
  } else {
3416
- await sleep2(120);
3937
+ await sleep3(120);
3417
3938
  if (!permissions.canSubmitForms) {
3418
3939
  return { success: false, error: "Form submission is not permitted", fieldResults };
3419
3940
  }
3941
+ const beforeSubmitTexts = captureSnapshot().texts;
3420
3942
  const submitResult = pressSubmitButton(
3421
3943
  {
3422
3944
  screen,
@@ -3436,7 +3958,8 @@ async function formFillHandler(payload, context) {
3436
3958
  };
3437
3959
  }
3438
3960
  const postSubmit = await waitForPostSubmitOutcome({
3439
- submitTargetId: submitResult.pressedTargetId
3961
+ submitTargetId: submitResult.pressedTargetId,
3962
+ beforeTexts: beforeSubmitTexts
3440
3963
  });
3441
3964
  if (postSubmit.status === "pending") {
3442
3965
  appilotsDebugWarn(`formFillHandler: submit still pending \u2014 ${postSubmit.message}`);
@@ -3463,6 +3986,9 @@ async function formFillHandler(payload, context) {
3463
3986
  }
3464
3987
  };
3465
3988
  }
3989
+ if (postSubmit.status === "unverified") {
3990
+ return { success: true, fieldResults, effect: "unknown" };
3991
+ }
3466
3992
  appilotsDebugLog("formFillHandler: submitAfterFill \u2014 submit completed");
3467
3993
  }
3468
3994
  }
@@ -3600,7 +4126,21 @@ function aggregateFieldDiagnoses(results, screen) {
3600
4126
  var FALLBACK_PAGE_PX = 600;
3601
4127
  var PAGE_FRACTION = 0.85;
3602
4128
  var POST_SCROLL_MOUNT_MS = 400;
3603
- var sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
4129
+ var PAGINATION_MAX_WAIT_MS = 4e3;
4130
+ var PAGINATION_POLL_MS = 150;
4131
+ var sleep4 = (ms) => new Promise((r) => setTimeout(r, ms));
4132
+ async function settleAfterScroll() {
4133
+ await sleep4(POST_SCROLL_MOUNT_MS);
4134
+ if (!probeLoadingState(void 0, getCurrentScreen()).loading) return;
4135
+ const deadline = Date.now() + PAGINATION_MAX_WAIT_MS;
4136
+ while (Date.now() < deadline) {
4137
+ await sleep4(PAGINATION_POLL_MS);
4138
+ if (!probeLoadingState(void 0, getCurrentScreen()).loading) {
4139
+ await sleep4(POST_SCROLL_MOUNT_MS);
4140
+ return;
4141
+ }
4142
+ }
4143
+ }
3604
4144
  function normalizeId2(value) {
3605
4145
  return String(value ?? "").toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "");
3606
4146
  }
@@ -3636,13 +4176,26 @@ function pageOffset(metrics, direction) {
3636
4176
  }
3637
4177
  return next;
3638
4178
  }
4179
+ async function finishScroll(entry, before) {
4180
+ await settleAfterScroll();
4181
+ const current2 = listRegistry.get(entry.id) ?? entry;
4182
+ const after = current2.getScrollMetrics?.();
4183
+ const grew = typeof entry.itemCount === "number" && typeof current2.itemCount === "number" && current2.itemCount > entry.itemCount;
4184
+ const moved = grew || !!(before && after && (Math.abs(after.offset - before.offset) > 2 || after.contentLength > before.contentLength + 2));
4185
+ const outcome = moved ? "moved" : before && after ? "no-progress" : "unverified";
4186
+ current2.exploration?.recordScroll(outcome);
4187
+ return {
4188
+ success: true,
4189
+ effect: moved ? "changed" : outcome === "no-progress" ? "none" : "unknown"
4190
+ };
4191
+ }
3639
4192
  async function scrollListHandler(payload, context) {
3640
4193
  const { permissions } = context;
3641
4194
  if (!permissions.canInteractUI) {
3642
4195
  return {
3643
4196
  success: false,
3644
- error: "UI interaction is not permitted",
3645
- diagnose: buildDiagnose("unknown", payload.listId)
4197
+ error: permissionDeniedMessage("UI interaction", "canInteractUI"),
4198
+ diagnose: { ...buildDiagnose("unknown", payload.listId), recoverable: false }
3646
4199
  };
3647
4200
  }
3648
4201
  const toIndex = typeof payload.toIndex === "number" ? payload.toIndex : void 0;
@@ -3670,6 +4223,15 @@ async function scrollListHandler(payload, context) {
3670
4223
  diagnose: buildDiagnose("component-not-found", payload.listId, candidates)
3671
4224
  };
3672
4225
  }
4226
+ const exploration = entry.exploration?.snapshot();
4227
+ if (exploration && (exploration.remainingScrollSteps === 0 || exploration.consecutiveNoProgress >= 2)) {
4228
+ return {
4229
+ success: false,
4230
+ effect: "none",
4231
+ 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.",
4232
+ diagnose: { ...buildDiagnose("validation", entry.id), recoverable: false }
4233
+ };
4234
+ }
3673
4235
  if (toIndex !== void 0) {
3674
4236
  if (entry.kind === "scroll") {
3675
4237
  return {
@@ -3685,17 +4247,16 @@ async function scrollListHandler(payload, context) {
3685
4247
  diagnose: buildDiagnose("validation", entry.id)
3686
4248
  };
3687
4249
  }
4250
+ const before = entry.getScrollMetrics?.();
3688
4251
  const zeroBased = toIndex - 1;
3689
4252
  if (entry.scrollToIndex?.(zeroBased)) {
3690
- await sleep3(POST_SCROLL_MOUNT_MS);
3691
- return { success: true };
4253
+ return finishScroll(entry, before);
3692
4254
  }
3693
4255
  const metrics2 = entry.getScrollMetrics?.();
3694
4256
  if (metrics2 && metrics2.contentLength > 0 && typeof entry.itemCount === "number" && entry.itemCount > 0) {
3695
4257
  const estimated = zeroBased / entry.itemCount * metrics2.contentLength;
3696
4258
  if (entry.scrollToOffset?.(Math.max(0, estimated))) {
3697
- await sleep3(POST_SCROLL_MOUNT_MS);
3698
- return { success: true };
4259
+ return finishScroll(entry, before);
3699
4260
  }
3700
4261
  }
3701
4262
  return {
@@ -3705,10 +4266,21 @@ async function scrollListHandler(payload, context) {
3705
4266
  };
3706
4267
  }
3707
4268
  const metrics = entry.getScrollMetrics?.();
4269
+ if (!metrics && exploration?.lastScroll === "unverified") {
4270
+ return {
4271
+ success: false,
4272
+ effect: "unknown",
4273
+ error: "Scroll offset is still unavailable after one probe. Cannot safely page again; do not infer the end or repeat the same absolute offset.",
4274
+ diagnose: { ...buildDiagnose("validation", entry.id), recoverable: false }
4275
+ };
4276
+ }
3708
4277
  const target = pageOffset(metrics, direction);
4278
+ if (metrics && Math.abs(target - metrics.offset) <= 2) {
4279
+ entry.exploration?.recordScroll("boundary");
4280
+ return { success: true, effect: "none" };
4281
+ }
3709
4282
  if (entry.scrollToOffset?.(target)) {
3710
- await sleep3(POST_SCROLL_MOUNT_MS);
3711
- return { success: true };
4283
+ return finishScroll(entry, metrics);
3712
4284
  }
3713
4285
  return {
3714
4286
  success: false,
@@ -3792,7 +4364,7 @@ function hashFieldValue(value) {
3792
4364
  }
3793
4365
  return (hash >>> 0).toString(36);
3794
4366
  }
3795
- var DISCRETE_FIELD_TYPES = /* @__PURE__ */ new Set(["select", "date"]);
4367
+ var DISCRETE_FIELD_TYPES = /* @__PURE__ */ new Set(["select", "date", "number"]);
3796
4368
  function discreteFieldSignature() {
3797
4369
  const sigs = [];
3798
4370
  for (const { id, kind } of componentRegistry.snapshot()) {
@@ -3806,12 +4378,23 @@ function discreteFieldSignature() {
3806
4378
  }
3807
4379
  return sigs.sort().join(",");
3808
4380
  }
3809
- async function waitForPostInteractionSettle(payload) {
4381
+ function capturePreInteractionBaseline(payload) {
4382
+ const targetId = targetIdFromPayload(payload);
4383
+ const screen = getCurrentScreen();
4384
+ const probe = probeLoadingState(targetId || null, screen);
4385
+ return {
4386
+ fingerprint: probe.fingerprint,
4387
+ fields: discreteFieldSignature(),
4388
+ screen,
4389
+ readable: probe.fingerprint !== ""
4390
+ };
4391
+ }
4392
+ async function waitForPostInteractionSettle(payload, preBaseline) {
3810
4393
  const targetId = targetIdFromPayload(payload);
3811
- const fromScreen = getCurrentScreen();
3812
- const baseline = probeLoadingState(targetId || null, fromScreen);
3813
- const baselineFields = discreteFieldSignature();
3814
- const readable = baseline.fingerprint !== "";
4394
+ const baseline = preBaseline ?? capturePreInteractionBaseline(payload);
4395
+ const fromScreen = baseline.screen;
4396
+ const baselineFields = baseline.fields;
4397
+ const readable = baseline.readable;
3815
4398
  await sleepMs(100);
3816
4399
  const afterPress = probeLoadingState(targetId || null, getCurrentScreen());
3817
4400
  const modalOrLoading = afterPress.modalOpen || afterPress.loading || afterPress.pressedDisabled;
@@ -3843,12 +4426,6 @@ async function waitForPostInteractionSettle(payload) {
3843
4426
  }
3844
4427
 
3845
4428
  // src/executor/ActionExecutor.ts
3846
- var DEFAULT_PERMISSIONS = {
3847
- canNavigate: true,
3848
- canFillForms: true,
3849
- canInteractUI: true,
3850
- canSubmitForms: true
3851
- };
3852
4429
  function validateActionPayload(action) {
3853
4430
  const invalid = (message, ids) => ({
3854
4431
  success: false,
@@ -3919,7 +4496,7 @@ async function executeAction(action, context) {
3919
4496
  appilotsDebugLog(
3920
4497
  `executeAction called: type="${action.type}", id="${action.id}", payload=${JSON.stringify(action.payload)}`
3921
4498
  );
3922
- const permissions = context.permissions ?? DEFAULT_PERMISSIONS;
4499
+ const permissions = resolveAgentPermissions(context.permissions);
3923
4500
  context.emit({
3924
4501
  type: "agent:action:start",
3925
4502
  timestamp: Date.now(),
@@ -3977,12 +4554,13 @@ async function executeAction(action, context) {
3977
4554
  break;
3978
4555
  case "ui_interaction": {
3979
4556
  const payload = action.payload;
4557
+ const baseline = capturePreInteractionBaseline(payload);
3980
4558
  result = uiInteractionHandler(payload, {
3981
4559
  permissions,
3982
4560
  registry: context.registry ?? getDefaultRegistry()
3983
4561
  });
3984
4562
  if (result.success) {
3985
- const settle = await waitForPostInteractionSettle(payload);
4563
+ const settle = await waitForPostInteractionSettle(payload, baseline);
3986
4564
  result.effect = result.effect ?? effectFor(payload, settle.effect);
3987
4565
  }
3988
4566
  break;
@@ -4070,18 +4648,28 @@ var _patchedAlert = (title, message, buttons, options) => {
4070
4648
  return suppression.originalAlert(title, message, buttons, options);
4071
4649
  }
4072
4650
  suppression.consumed = true;
4651
+ const report = (handlerCompleted) => suppression.onHandled?.({
4652
+ title: title.slice(0, 300),
4653
+ message: message?.slice(0, 1e3),
4654
+ buttonLabel: button.text?.slice(0, 160),
4655
+ handlerCompleted
4656
+ });
4073
4657
  appilotsDebugLog("Suppressing native Alert during already-confirmed destructive agent action");
4074
4658
  try {
4075
- suppression.pending = Promise.resolve(button.onPress()).then(() => void 0).catch((err) => reportHandlerFailure(err));
4659
+ suppression.pending = Promise.resolve(button.onPress()).then(() => report(true)).catch((err) => {
4660
+ reportHandlerFailure(err);
4661
+ report(false);
4662
+ });
4076
4663
  } catch (err) {
4077
4664
  reportHandlerFailure(err);
4078
4665
  suppression.pending = null;
4666
+ report(false);
4079
4667
  }
4080
4668
  };
4081
4669
  function reportHandlerFailure(err) {
4082
4670
  console.error("[Appilots] The app\u2019s Alert handler threw while being auto-confirmed", err);
4083
4671
  }
4084
- async function runWithConfirmedDestructiveContext(enabled, host, fn) {
4672
+ async function runWithConfirmedDestructiveContext(enabled, host, fn, onHandled) {
4085
4673
  if (!enabled) return fn();
4086
4674
  if (_active) {
4087
4675
  _active.depth += 1;
@@ -4102,6 +4690,7 @@ async function runWithConfirmedDestructiveContext(enabled, host, fn) {
4102
4690
  depth: 1,
4103
4691
  consumed: false,
4104
4692
  pending: null,
4693
+ onHandled,
4105
4694
  timer: null
4106
4695
  };
4107
4696
  _active = suppression;
@@ -4135,9 +4724,14 @@ function resolveScreenMetadata() {
4135
4724
  return screen ? getScreenMetadata(screen) : void 0;
4136
4725
  }
4137
4726
  function buildAgentContext(extras = {}) {
4138
- if (extras.newMission === true) deltaTracker.reset();
4727
+ if (extras.newMission === true) {
4728
+ deltaTracker.reset();
4729
+ for (const entry of listRegistry.snapshot()) entry.exploration?.reset();
4730
+ }
4139
4731
  const context = {};
4140
4732
  context.platform = "react-native";
4733
+ context.missionProtocol = 1;
4734
+ context.deviceLocale = detectDeviceLocale() ?? "en";
4141
4735
  const screen = getCurrentScreen();
4142
4736
  if (screen) context.currentScreen = screen;
4143
4737
  const navigationState = getNavigationStateSnapshot();
@@ -4146,6 +4740,12 @@ function buildAgentContext(extras = {}) {
4146
4740
  if (screenMeta) context.screenMetadata = screenMeta;
4147
4741
  try {
4148
4742
  const snapshot = captureSnapshot();
4743
+ for (const list of snapshot.lists) {
4744
+ const memory = list.id ? listRegistry.get(list.id)?.exploration : void 0;
4745
+ if (!memory) continue;
4746
+ memory.observe(list.items);
4747
+ list.exploration = memory.snapshot();
4748
+ }
4149
4749
  const delta = deltaTracker.observe(snapshot);
4150
4750
  if (delta) snapshot.delta = delta;
4151
4751
  context.snapshot = snapshot;
@@ -4157,9 +4757,20 @@ function buildAgentContext(extras = {}) {
4157
4757
  if (filtered.length > 0) context.registeredComponents = filtered;
4158
4758
  const registeredLists = listRegistry.snapshot();
4159
4759
  if (registeredLists.length > 0) {
4160
- context.registeredLists = registeredLists.map(
4161
- ({ scrollToIndex, scrollToOffset, getScrollMetrics, dataPreview, ...rest }) => rest
4760
+ const snapshot = context.snapshot;
4761
+ const activeIds = new Set(
4762
+ [...snapshot?.lists ?? [], ...snapshot?.scrollables ?? []].map((l) => l.id)
4162
4763
  );
4764
+ context.registeredLists = registeredLists.filter((entry) => activeIds.has(entry.id)).map(({ id, kind, component, itemCount, totalItemCount, label, refreshing, empty }) => ({
4765
+ id,
4766
+ kind,
4767
+ component,
4768
+ itemCount,
4769
+ totalItemCount,
4770
+ label,
4771
+ refreshing,
4772
+ empty
4773
+ }));
4163
4774
  }
4164
4775
  if (extras.loadingPending === true) context.loadingPending = true;
4165
4776
  return context;
@@ -4169,6 +4780,7 @@ async function settleTurn(input) {
4169
4780
  let loadingPending = false;
4170
4781
  const { turnActions, pressedComponentId: pressedId, navigateTargetScreen } = input;
4171
4782
  const successById = new Map(input.results.map((r) => [r.actionId, r.success]));
4783
+ const effectById = new Map(input.results.map((r) => [r.actionId, r.effect]));
4172
4784
  const typeById = new Map(input.results.map((r) => [r.actionId, r.type]));
4173
4785
  const pressedAction = turnActions.find(
4174
4786
  (a) => a.type === "ui_interaction" && a.payload?.componentId === pressedId
@@ -4244,6 +4856,7 @@ async function settleTurn(input) {
4244
4856
  );
4245
4857
  if (!postPressSettle.transitioned && baselineFingerprint === afterFingerprint) {
4246
4858
  for (const action of listPressActions) {
4859
+ if (effectById.get(action.id) === "changed") continue;
4247
4860
  if (successById.get(action.id)) {
4248
4861
  toolWithoutEffectIds.add(action.id);
4249
4862
  }
@@ -4261,18 +4874,40 @@ var reactNativeChatAdapter = {
4261
4874
  };
4262
4875
  function createReactNativeActionRunner(getOptions) {
4263
4876
  return {
4877
+ buildMissionContext: () => reactNativeChatAdapter.buildContext(),
4264
4878
  async execute(action, { confirmedDestructive }) {
4265
- const { permissions, emit, navigationRef, suppressNativeConfirm } = getOptions();
4879
+ const { permissions, emit: emit2, navigationRef, suppressNativeConfirm } = getOptions();
4266
4880
  const navRef = navigationRef?.current ? navigationRef : { current: getNavigationRef() };
4267
- return runWithConfirmedDestructiveContext(
4881
+ let receipt;
4882
+ let terminal;
4883
+ const result = await runWithConfirmedDestructiveContext(
4268
4884
  confirmedDestructive && suppressNativeConfirm !== false,
4269
4885
  Alert,
4270
4886
  () => executeAction(action, {
4271
4887
  navigationRef: navRef,
4272
4888
  permissions,
4273
- emit
4274
- })
4889
+ emit: (event) => {
4890
+ if (confirmedDestructive && (event.type === "agent:action:complete" || event.type === "agent:action:error"))
4891
+ terminal = event;
4892
+ else emit2(event);
4893
+ }
4894
+ }),
4895
+ (handled) => {
4896
+ receipt = handled;
4897
+ }
4275
4898
  );
4899
+ const failed = receipt?.handlerCompleted === false;
4900
+ if (terminal)
4901
+ emit2({
4902
+ ...terminal,
4903
+ type: failed ? "agent:action:error" : terminal.type,
4904
+ data: {
4905
+ ...terminal.data,
4906
+ ...receipt ? { nativeConfirmation: receipt } : {},
4907
+ ...failed ? { success: false, error: "Native confirmation callback failed" } : {}
4908
+ }
4909
+ });
4910
+ return failed ? { ...result, success: false, error: "Native confirmation callback failed" } : result;
4276
4911
  },
4277
4912
  getCurrentScreen,
4278
4913
  getCurrentScreenSignature,
@@ -4285,16 +4920,16 @@ function createReactNativeActionRunner(getOptions) {
4285
4920
 
4286
4921
  // src/hooks/useAppilotsChat.ts
4287
4922
  function useAppilotsChat(options = {}) {
4288
- const { client, emit, subscribe } = useAppilotsContext();
4923
+ const { client, emit: emit2, subscribe } = useAppilotsContext();
4289
4924
  const machine = useMemo(
4290
4925
  () => new ChatSessionMachine(
4291
- { client, adapter: reactNativeChatAdapter, emit, warn: appilotsDebugWarn },
4926
+ { client, adapter: reactNativeChatAdapter, emit: emit2, warn: appilotsDebugWarn },
4292
4927
  options
4293
4928
  ),
4294
4929
  // A new machine per client/provider instance — options are pushed in
4295
4930
  // below so changing copy never resets the transcript.
4296
4931
  // eslint-disable-next-line react-hooks/exhaustive-deps
4297
- [client, emit]
4932
+ [client, emit2]
4298
4933
  );
4299
4934
  const optionsRef = useRef(options);
4300
4935
  optionsRef.current = options;
@@ -4302,6 +4937,14 @@ function useAppilotsChat(options = {}) {
4302
4937
  const state = useSyncExternalStore(machine.subscribeState, machine.getState, machine.getState);
4303
4938
  useEffect(() => subscribe(machine.handleEvent), [subscribe, machine]);
4304
4939
  useEffect(() => () => machine.dispose(), [machine]);
4940
+ useEffect(() => {
4941
+ void machine.restoreMission();
4942
+ const subscription = AppState?.addEventListener?.("change", (next) => {
4943
+ if (next !== "active" && machine.getState().mission?.status === "running")
4944
+ void machine.pauseMission();
4945
+ });
4946
+ return () => subscription?.remove();
4947
+ }, [machine]);
4305
4948
  const requestHuman = useCallback(
4306
4949
  (reason) => machine.requestHuman(reason),
4307
4950
  [machine]
@@ -4311,7 +4954,13 @@ function useAppilotsChat(options = {}) {
4311
4954
  const clearMessages = useCallback(() => machine.clearMessages(), [machine]);
4312
4955
  const clearError = useCallback(() => machine.clearError(), [machine]);
4313
4956
  return {
4957
+ mission: state.mission,
4958
+ pauseMission: () => machine.pauseMission(),
4959
+ resumeMission: () => machine.resumeMission(),
4960
+ cancelMission: () => machine.cancelMission(),
4961
+ hasStartedActing: state.hasStartedActing,
4314
4962
  messages: state.messages,
4963
+ replyLocale: state.replyLocale,
4315
4964
  isLoading: state.isLoading,
4316
4965
  loadingStatusKey: state.loadingStatusKey,
4317
4966
  error: state.error,
@@ -4324,24 +4973,18 @@ function useAppilotsChat(options = {}) {
4324
4973
  clearError
4325
4974
  };
4326
4975
  }
4327
- var DEFAULT_PERMISSIONS2 = {
4328
- canNavigate: true,
4329
- canFillForms: true,
4330
- canInteractUI: true,
4331
- canSubmitForms: true
4332
- };
4333
4976
  function useAppilotsActions(options = {}) {
4334
- const { client, subscribe, config, emit } = useAppilotsContext();
4977
+ const { client, subscribe, config, emit: emit2 } = useAppilotsContext();
4335
4978
  const { navigationRef, autoExecute = false } = options;
4336
4979
  const runnerOptionsRef = useRef({
4337
- permissions: config.permissions ?? DEFAULT_PERMISSIONS2,
4338
- emit,
4980
+ permissions: resolveAgentPermissions(config.permissions),
4981
+ emit: emit2,
4339
4982
  navigationRef,
4340
4983
  suppressNativeConfirm: config.suppressNativeConfirm
4341
4984
  });
4342
4985
  runnerOptionsRef.current = {
4343
- permissions: config.permissions ?? DEFAULT_PERMISSIONS2,
4344
- emit,
4986
+ permissions: resolveAgentPermissions(config.permissions),
4987
+ emit: emit2,
4345
4988
  navigationRef,
4346
4989
  suppressNativeConfirm: config.suppressNativeConfirm
4347
4990
  };
@@ -4350,13 +4993,13 @@ function useAppilotsActions(options = {}) {
4350
4993
  {
4351
4994
  client,
4352
4995
  adapter: createReactNativeActionRunner(() => runnerOptionsRef.current),
4353
- emit,
4996
+ emit: emit2,
4354
4997
  warn: appilotsDebugWarn
4355
4998
  },
4356
4999
  { autoExecute }
4357
5000
  ),
4358
5001
  // eslint-disable-next-line react-hooks/exhaustive-deps
4359
- [client, emit]
5002
+ [client, emit2]
4360
5003
  );
4361
5004
  machine.setOptions({ autoExecute });
4362
5005
  const state = useSyncExternalStore(machine.subscribeState, machine.getState, machine.getState);
@@ -4446,9 +5089,9 @@ function useSuggestedPrompts(options) {
4446
5089
  if (!enabled) return;
4447
5090
  let lastScreen = getCurrentScreen();
4448
5091
  const interval = setInterval(() => {
4449
- const current = getCurrentScreen();
4450
- if (current !== lastScreen) {
4451
- lastScreen = current;
5092
+ const current2 = getCurrentScreen();
5093
+ if (current2 !== lastScreen) {
5094
+ lastScreen = current2;
4452
5095
  setRefreshTick((t) => t + 1);
4453
5096
  }
4454
5097
  }, 800);
@@ -4461,7 +5104,7 @@ function useSuggestedPrompts(options) {
4461
5104
  };
4462
5105
  }
4463
5106
  function useAppilotsNavigation() {
4464
- const { emit } = useAppilotsContext();
5107
+ const { emit: emit2 } = useAppilotsContext();
4465
5108
  const [currentScreen, setCurrentScreenState] = useState(null);
4466
5109
  const [navigationHistory, setNavigationHistory] = useState([]);
4467
5110
  const navigationRef = useRef(null);
@@ -4470,13 +5113,13 @@ function useAppilotsNavigation() {
4470
5113
  setCurrentScreenState(screenName);
4471
5114
  setCurrentScreen(screenName);
4472
5115
  setNavigationHistory((prev) => [...prev, screenName]);
4473
- emit({
5116
+ emit2({
4474
5117
  type: "navigation:change",
4475
5118
  timestamp: Date.now(),
4476
5119
  data: { screenName }
4477
5120
  });
4478
5121
  },
4479
- [emit]
5122
+ [emit2]
4480
5123
  );
4481
5124
  useEffect(() => {
4482
5125
  const nav = navigationRef.current;
@@ -4526,6 +5169,11 @@ function useAppilots() {
4526
5169
  rejectAction: actions.rejectAction
4527
5170
  };
4528
5171
  }
5172
+ function useAppilotsElementProps(id) {
5173
+ return useMemo(() => ({ testID: id }), [id]);
5174
+ }
5175
+
5176
+ // src/hooks/useAppilotsField.ts
4529
5177
  function useAppilotsField(id, options) {
4530
5178
  const { value, onChangeText, ref, fieldType, label, screen } = options;
4531
5179
  const registry = useResolvedRegistry();
@@ -4553,6 +5201,7 @@ function useAppilotsField(id, options) {
4553
5201
  registry.unregister(id);
4554
5202
  };
4555
5203
  }, [id, registry, getValue, setValue, focus, ref, fieldType, label, screen]);
5204
+ return useAppilotsElementProps(id);
4556
5205
  }
4557
5206
  function useAppilotsTarget(id, options) {
4558
5207
  const { onPress, onLongPress, onScrollTo, label, screen } = options;
@@ -4580,6 +5229,7 @@ function useAppilotsTarget(id, options) {
4580
5229
  registry.unregister(id);
4581
5230
  };
4582
5231
  }, [id, registry, press, longPress, scrollTo, onLongPress, onScrollTo, label, screen]);
5232
+ return useAppilotsElementProps(id);
4583
5233
  }
4584
5234
  function useAppilotsToggle(id, options) {
4585
5235
  const { value, onValueChange, label, screen } = options;
@@ -4603,6 +5253,7 @@ function useAppilotsToggle(id, options) {
4603
5253
  registry.unregister(id);
4604
5254
  };
4605
5255
  }, [id, registry, getValue, setValue, label, screen]);
5256
+ return useAppilotsElementProps(id);
4606
5257
  }
4607
5258
  function useAppilotsSlider(id, options) {
4608
5259
  const { value, onValueChange, min, max, step, label, screen } = options;
@@ -4629,6 +5280,7 @@ function useAppilotsSlider(id, options) {
4629
5280
  registry.unregister(id);
4630
5281
  };
4631
5282
  }, [id, registry, getValue, setValue, min, max, step, label, screen]);
5283
+ return useAppilotsElementProps(id);
4632
5284
  }
4633
5285
 
4634
- export { appilotsSelfCheck, captureSnapshot, executeAction, shouldShowSuggestedPrompts, useAppilots, useAppilotsActions, useAppilotsChat, useAppilotsField, useAppilotsNavigation, useAppilotsSlider, useAppilotsTarget, useAppilotsToggle, useSuggestedPrompts };
5286
+ export { AppilotsI18nProvider, REVEAL_MARGIN, SPOTLIGHT_TTL_MS, appilotsSelfCheck, captureSnapshot, detectDeviceLocale, executeAction, getSpotlightMode, getSpotlightTarget, offsetToReveal, resolveLocale, revealTarget, setSpotlightMode, setSpotlightTarget, shouldShowSuggestedPrompts, subscribeSpotlight, subscribeSpotlightAnnouncement, useAppilots, useAppilotsActions, useAppilotsChat, useAppilotsField, useAppilotsI18n, useAppilotsNavigation, useAppilotsSlider, useAppilotsTarget, useAppilotsToggle, useSuggestedPrompts };