@appilots/sdk 0.8.0 → 0.11.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.
@@ -14,6 +14,153 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
14
14
  throw Error('Dynamic require of "' + x + '" is not supported');
15
15
  });
16
16
 
17
+ // ../shared/dist/chunk-UEMUBXFH.mjs
18
+ var DEFAULT_API_BASE_URL = "https://api.appilots.com";
19
+ var APPILOTS_API_PATH_PREFIX = "/api/v1";
20
+ function normalizeApiBaseUrl(configured) {
21
+ const raw = (configured ?? DEFAULT_API_BASE_URL).trim().replace(/\/+$/, "");
22
+ if (!raw) return `${DEFAULT_API_BASE_URL}${APPILOTS_API_PATH_PREFIX}`;
23
+ if (new RegExp(`${APPILOTS_API_PATH_PREFIX}(/|$)`).test(raw)) return raw;
24
+ return `${raw}${APPILOTS_API_PATH_PREFIX}`;
25
+ }
26
+
27
+ // ../shared/dist/screen-text/index.mjs
28
+ function build(table, extra = []) {
29
+ return [...Object.values(table).flat(), ...extra].join("|");
30
+ }
31
+ var VALIDATION = {
32
+ pt: [
33
+ "inv[a\xE1]lid",
34
+ "obrigat[o\xF3]ri",
35
+ "j[a\xE1]\\s+existe",
36
+ "duplicad",
37
+ "muito\\s+curto",
38
+ "n[a\xE3]o\\s+(?:foi|pode)",
39
+ "deve\\s+ser",
40
+ "selecione",
41
+ "escolha",
42
+ "preencha",
43
+ "corrija"
44
+ ],
45
+ en: [
46
+ "invalid",
47
+ "required",
48
+ "already\\s+exists",
49
+ "too\\s+short",
50
+ "must\\s+be",
51
+ "select\\s+(?:the|a|an)",
52
+ "please\\s+(?:enter|choose|select|fill)",
53
+ "can'?t\\s+be\\s+(?:empty|blank)",
54
+ "cannot\\s+be\\s+(?:empty|blank)"
55
+ ],
56
+ es: [
57
+ "obligatori",
58
+ "ya\\s+existe",
59
+ "demasiado\\s+corto",
60
+ "debe\\s+ser",
61
+ "seleccione",
62
+ "elija",
63
+ "complet[ae]\\s+(?:el|los|este)",
64
+ "no\\s+puede\\s+(?:estar|quedar)\\s+vac"
65
+ ],
66
+ fr: [
67
+ "invalide",
68
+ "obligatoire",
69
+ "existe\\s+d[e\xE9]j[a\xE0]",
70
+ "trop\\s+court",
71
+ "doit\\s+[\xEAe]tre",
72
+ "s[e\xE9]lectionnez",
73
+ "choisissez",
74
+ "veuillez\\s+(?:saisir|choisir|remplir|s[e\xE9]lectionner)",
75
+ "ne\\s+peut\\s+pas\\s+[\xEAe]tre\\s+vide"
76
+ ]
77
+ };
78
+ var VALIDATION_SHARED = ["format[oa]?\\b", "\\bformat\\b"];
79
+ var WRITE_SUCCESS = {
80
+ pt: [
81
+ "cadastrad[oa]s?\\s+com\\s+sucesso",
82
+ "registrad[oa]s?\\s+com\\s+sucesso",
83
+ "salv[oa]s?\\s+com\\s+sucesso",
84
+ "criad[oa]s?\\s+com\\s+sucesso",
85
+ "atualizad[oa]s?\\s+com\\s+sucesso",
86
+ "foi\\s+cadastrad[oa]",
87
+ "foi\\s+registrad[oa]",
88
+ "foi\\s+salv[oa]",
89
+ "foi\\s+criad[oa]"
90
+ ],
91
+ en: [
92
+ "successfully\\s+(?:registered|saved|created|updated|added)",
93
+ "(?:has\\s+been|was)\\s+(?:registered|saved|created|updated|added)"
94
+ ],
95
+ es: [
96
+ "registrad[oa]s?\\s+(?:exitosamente|correctamente|con\\s+[e\xE9]xito)",
97
+ "guardad[oa]s?\\s+(?:correctamente|con\\s+[e\xE9]xito)",
98
+ "cread[oa]s?\\s+(?:correctamente|con\\s+[e\xE9]xito)",
99
+ "se\\s+ha\\s+(?:guardado|creado|registrado|actualizado)"
100
+ ],
101
+ fr: [
102
+ "(?:enregistr|cr[e\xE9]|ajout|mis\\s+[a\xE0]\\s+jour)[e\xE9]?e?s?\\s+avec\\s+succ[e\xE8]s",
103
+ "a\\s+[e\xE9]t[e\xE9]\\s+(?:enregistr|cr[e\xE9]|ajout|sauvegard)[e\xE9]e?"
104
+ ]
105
+ };
106
+ var COMPLETION_CLAIM = {
107
+ pt: [
108
+ "j[a\xE1]\\s+(?:cadastrei|registrei|salvei|criei)",
109
+ "conclu[i\xED]\\s+o\\s+cadastro",
110
+ "cadastro\\s+(?:foi\\s+)?(?:conclu[i\xED]do|realizado|feito)"
111
+ ],
112
+ en: [
113
+ "i(?:'|\u2019)?ve\\s+(?:registered|saved|created|added)",
114
+ "i\\s+have\\s+(?:registered|saved|created|added)"
115
+ ],
116
+ es: ["ya\\s+(?:lo\\s+|la\\s+)?(?:registr[e\xE9]|guard[e\xE9]|cre[e\xE9])"],
117
+ fr: ["j(?:e\\s+l(?:'|\u2019)ai|(?:'|\u2019)ai)\\s+(?:enregistr|cr[e\xE9]|ajout|sauvegard)[e\xE9]"]
118
+ };
119
+ var SUBMIT_CONTROL = {
120
+ pt: ["cadastrar", "registrar", "salvar", "enviar", "confirmar"],
121
+ en: ["submit", "register", "save", "confirm"],
122
+ es: ["guardar", "registrar", "enviar", "confirmar", "aceptar"],
123
+ fr: ["enregistrer", "soumettre", "envoyer", "confirmer", "valider"]
124
+ };
125
+ var SUBMIT_LABEL_EXTRA = {
126
+ pt: ["criar", "adicionar", "concluir", "finalizar"],
127
+ en: ["create", "add", "done", "finish", "continue"],
128
+ es: ["crear", "a[\xF1n]adir", "agregar", "continuar", "finalizar"],
129
+ fr: ["cr[e\xE9]er", "ajouter", "terminer", "continuer"]
130
+ };
131
+ var CREATE_EDIT_ROUTE = {
132
+ pt: ["cadastr", "criar", "novo", "nova", "registr", "edit"],
133
+ en: ["create", "edit", "form", "new", "add"],
134
+ es: ["crear", "nuevo", "nueva", "editar", "alta"],
135
+ fr: ["cr[e\xE9]er", "nouveau", "nouvelle", "modifier", "ajout"]
136
+ };
137
+ var WRITE_INTENT = {
138
+ pt: ["cadastr\\w*", "registr\\w*", "cria\\w*", "adiciona\\w*", "salv\\w*"],
139
+ en: [
140
+ "creat(?:e|es|ed|ing|ion)",
141
+ "save[sd]?",
142
+ "saving",
143
+ "add(?:s|ed|ing)?",
144
+ "submit\\w*",
145
+ "register\\w*"
146
+ ],
147
+ es: ["crea\\w*", "guarda\\w*", "a[n\xF1]ad\\w*", "agrega\\w*", "registra\\w*"],
148
+ fr: ["cr[e\xE9][e\xE9]\\w*", "enregistr\\w*", "ajout\\w*", "sauvegard\\w*", "soumet\\w*"]
149
+ };
150
+ var FORM_VALIDATION_TEXT_RE = new RegExp(`(${build(VALIDATION, VALIDATION_SHARED)})`, "i");
151
+ var FORM_WRITE_SUCCESS_TEXT_RE = new RegExp(`\\b(${build(WRITE_SUCCESS)})`, "i");
152
+ new RegExp(`\\b(${build(WRITE_INTENT)})\\b`, "i");
153
+ new RegExp(
154
+ `\\b(${build(WRITE_SUCCESS)}|${build(COMPLETION_CLAIM)})`,
155
+ "i"
156
+ );
157
+ var SUBMIT_CONTROL_RE = new RegExp(`(${build(SUBMIT_CONTROL)})`, "i");
158
+ new RegExp(
159
+ `(${build(SUBMIT_CONTROL)}|${build(SUBMIT_LABEL_EXTRA)})`,
160
+ "i"
161
+ );
162
+ var CREATE_EDIT_ROUTE_RE = new RegExp(`(${build(CREATE_EDIT_ROUTE)})`, "i");
163
+
17
164
  // ../shared/dist/chunk-43YQ52Q7.mjs
18
165
  var SENSITIVE_FIELD_NAME_RE = /(\b|_|-)(password|senha|contrase[ñn]a|mot[\s_-]*de[\s_-]*passe|passwd|pwd|secret|token|api[\s_-]*key|access[\s_-]*key|private[\s_-]*key|cvv|cvc|cvn|cvc2|cid|pin(?:[\s_-]*code)?|otp|one[\s_-]*time[\s_-]*code|2fa|mfa|security[\s_-]*code|c[óo]digo[\s_-]*seguran[çc]a|c[óo]digo[\s_-]*verifica[çc][ãa]o|verification[\s_-]*code)(\b|_|-)/i;
19
166
  function isSensitiveFieldName(id, label, placeholder, type) {
@@ -28,6 +175,39 @@ function isSensitiveFieldName(id, label, placeholder, type) {
28
175
 
29
176
  // ../client-core/dist/index.mjs
30
177
  var SDK_VERSION = "0.1.0";
178
+ var _enabled;
179
+ var _resolve;
180
+ function setAppilotsDebugEnabled(enabled) {
181
+ _enabled = enabled;
182
+ }
183
+ function setAppilotsDebugResolver(resolve) {
184
+ _resolve = resolve;
185
+ }
186
+ function isAppilotsDebugEnabled() {
187
+ if (_enabled !== void 0) return _enabled;
188
+ try {
189
+ return _resolve?.() === true;
190
+ } catch {
191
+ return false;
192
+ }
193
+ }
194
+ function appilotsDebugLog(...args) {
195
+ if (!isAppilotsDebugEnabled()) return;
196
+ console.log("[Appilots]", ...args);
197
+ }
198
+ function appilotsDebugWarn(...args) {
199
+ if (!isAppilotsDebugEnabled()) return;
200
+ console.log("[Appilots]", ...args);
201
+ }
202
+ function describeValue(value) {
203
+ if (value === void 0) return "undefined";
204
+ if (value === null) return "null";
205
+ if (typeof value === "string") return `string(len=${value.length})`;
206
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
207
+ if (Array.isArray(value)) return `array(len=${value.length})`;
208
+ if (typeof value === "object") return `object(keys=${Object.keys(value).length})`;
209
+ return typeof value;
210
+ }
31
211
  var StreamTransportError = class extends Error {
32
212
  constructor(message) {
33
213
  super(message);
@@ -58,6 +238,13 @@ function parseRetryAfter(...candidates) {
58
238
  return 60;
59
239
  }
60
240
  var CONTINUATION_TIMEOUT_MS = 15e4;
241
+ function safeJson(text) {
242
+ try {
243
+ return JSON.parse(text || "{}");
244
+ } catch {
245
+ return null;
246
+ }
247
+ }
61
248
  var AppilotsClient = class {
62
249
  baseUrl;
63
250
  projectId;
@@ -72,7 +259,7 @@ var AppilotsClient = class {
72
259
  identified = false;
73
260
  constructor(options) {
74
261
  this.projectId = options.projectId;
75
- this.baseUrl = (options.apiBaseUrl ?? "https://api.appilots.com").replace(/\/$/, "");
262
+ this.baseUrl = normalizeApiBaseUrl(options.apiBaseUrl);
76
263
  this.timeout = options.timeout ?? 6e4;
77
264
  this.debug = options.debug ?? false;
78
265
  this.mcpVersion = options.mcpVersion;
@@ -91,8 +278,25 @@ var AppilotsClient = class {
91
278
  ...options.headers
92
279
  };
93
280
  }
281
+ /**
282
+ * A 404 on an `/agent/*` path is almost never a missing record — those
283
+ * routes are static. It means the request reached SOMETHING that is not
284
+ * this API, or reached it at the wrong mount point, and the server's own
285
+ * message ("Route not found") tells the integrator nothing about which.
286
+ *
287
+ * The symptom is unmistakable once you have seen it: every message
288
+ * fails, including a plain "hello" that needs no tool at all, because
289
+ * nothing ever reaches the agent. Left as-is it reads like the agent is
290
+ * broken.
291
+ */
292
+ explainIfMisroutedBaseUrl(status, json, fallback) {
293
+ if (status !== 404) return fallback;
294
+ const code = json?.error?.code;
295
+ if (code !== "NOT_FOUND") return fallback;
296
+ return `${fallback} \u2014 the Appilots API answered 404 for every path under ${this.baseUrl}. That usually means apiBaseUrl points somewhere this API is not mounted. The hosted API is https://api.appilots.com${APPILOTS_API_PATH_PREFIX}; a local stack is http://localhost:4000${APPILOTS_API_PATH_PREFIX}.`;
297
+ }
94
298
  log(...args) {
95
- if (this.debug) console.log("[AppilotsClient]", ...args);
299
+ if (this.debug) appilotsDebugLog("[AppilotsClient]", ...args);
96
300
  }
97
301
  describeNonJsonResponse(url, response, body) {
98
302
  const contentType = response.headers?.get?.("content-type") ?? "unknown content-type";
@@ -126,13 +330,10 @@ var AppilotsClient = class {
126
330
  if (response.status === 429) {
127
331
  throw new RateLimitedError(
128
332
  errorMsg,
129
- parseRetryAfter(
130
- json?.error?.retryAfterSeconds,
131
- response.headers?.get?.("retry-after")
132
- )
333
+ parseRetryAfter(json?.error?.retryAfterSeconds, response.headers?.get?.("retry-after"))
133
334
  );
134
335
  }
135
- throw new Error(errorMsg);
336
+ throw new Error(this.explainIfMisroutedBaseUrl(response.status, json, errorMsg));
136
337
  }
137
338
  return json.data ?? json;
138
339
  } finally {
@@ -247,7 +448,11 @@ var AppilotsClient = class {
247
448
  content: data.message.summary,
248
449
  timestamp: Date.now()
249
450
  } : void 0;
250
- this.log("Response received:", { messageId: message.id, actions: actions?.length ?? 0, hasSummary: !!summaryMessage });
451
+ this.log("Response received:", {
452
+ messageId: message.id,
453
+ actions: actions?.length ?? 0,
454
+ hasSummary: !!summaryMessage
455
+ });
251
456
  return {
252
457
  sessionId: this.sessionId,
253
458
  message,
@@ -388,17 +593,17 @@ var AppilotsClient = class {
388
593
  fail(
389
594
  new RateLimitedError(
390
595
  message2,
391
- parseRetryAfter(
392
- retryAfterSeconds,
393
- xhr.getResponseHeader?.("retry-after")
394
- )
596
+ parseRetryAfter(retryAfterSeconds, xhr.getResponseHeader?.("retry-after"))
395
597
  )
396
598
  );
397
599
  return;
398
600
  }
399
- fail(
400
- sawAnyEvent ? new Error(message2) : new StreamTransportError(message2)
601
+ const explained = this.explainIfMisroutedBaseUrl(
602
+ xhr.status,
603
+ safeJson(xhr.responseText),
604
+ message2
401
605
  );
606
+ fail(sawAnyEvent ? new Error(explained) : new StreamTransportError(explained));
402
607
  return;
403
608
  }
404
609
  if (!doneEvent) {
@@ -580,7 +785,9 @@ var AppilotsClient = class {
580
785
  };
581
786
  case "navigate":
582
787
  return {
583
- screenName: payload.screenName ?? "",
788
+ // No `?? ''` here: an empty string would make a goBack look
789
+ // like a navigate to a nameless screen further down (#398).
790
+ screenName: typeof payload.screenName === "string" ? payload.screenName : void 0,
584
791
  params: payload.params,
585
792
  navigationAction: payload.navigationAction ?? "navigate",
586
793
  path: Array.isArray(payload.path) ? payload.path : void 0
@@ -1113,8 +1320,15 @@ function subscribeAppilotsDebugTraces(listener) {
1113
1320
  };
1114
1321
  }
1115
1322
  var INTERNAL_MARKER_RE = /^\[(?:Permission policy notice|Recovery exhausted|User rejected the proposed action)[^\]]*\]:\s*/i;
1116
- var TECHNICAL_TRACE_RE = /(^|\n)\s*(?:Results:|[-*]\s*[✓✗]\s+|(?:press|click|tap|navigate|form_fill|ui_interaction|scroll_list)\b|(?:Cliquei|Clicked)\s+em\s+(?:el:|list-\d+-item-\d+|[A-Za-z]+:))/i;
1117
- var TECHNICAL_SELECTOR_RE = /\b(?:el:[^\s]+|list-\d+-item-\d+|[A-Za-z][\w-]*:(?!\/\/)[^\s]+|(?:testID|test-id|accessibilityId|accessibility-id)[:=_-][^\s]+)/i;
1323
+ var INTERNAL_ID_SRC = 'el:[^\\s".]+|list-\\d+-item-\\d+';
1324
+ var TECHNICAL_FIELD_SRC = "\\b(?:targetId|componentId|screenName|actionId|fieldId|elementId|testID|test-id|accessibilityId|accessibility-id)\\s*[:=_-]\\s*\\S+";
1325
+ var TECHNICAL_SELECTOR_RE = new RegExp(`${INTERNAL_ID_SRC}|${TECHNICAL_FIELD_SRC}`, "i");
1326
+ var TOOL_NAME_SRC = "(?:form_fill|ui_interaction|scroll_list)\\b";
1327
+ var TOOL_VERB_WITH_ID_SRC = `(?:press|click|tap|navigate)\\w*\\b[^\\n]{0,40}?(?:${INTERNAL_ID_SRC})`;
1328
+ var TECHNICAL_TRACE_RE = new RegExp(
1329
+ `(^|\\n)\\s*(?:Results:|[-*]\\s*[\u2713\u2717]\\s+|${TOOL_NAME_SRC}|${TOOL_VERB_WITH_ID_SRC}|(?:Cliquei|Clicked)\\s+em\\s+(?:${INTERNAL_ID_SRC}|[A-Za-z][\\w-]*:))`,
1330
+ "i"
1331
+ );
1118
1332
  function sanitizeVisibleAssistantContent(content) {
1119
1333
  return content.split("\n").map((line) => line.replace(INTERNAL_MARKER_RE, "").trimEnd()).filter((line) => !INTERNAL_MARKER_RE.test(line)).join("\n").trim();
1120
1334
  }
@@ -1122,7 +1336,7 @@ function humanizeAssistantFallback(content) {
1122
1336
  let text = sanitizeVisibleAssistantContent(content).replace(/\blist-\d+-item-\d+\b/gi, "o item da lista").replace(/\bel:[^\s".]+/gi, "o elemento").replace(
1123
1337
  /"\s*(?:press|click|tap|navigate|form_fill|ui_interaction|scroll_list)\s+on\s+[^"]+"/gi,
1124
1338
  '"a a\xE7\xE3o solicitada"'
1125
- ).replace(/\bsubscription-cancel-[^\s"]+/gi, "cancelamento de assinatura").trim();
1339
+ ).trim();
1126
1340
  if (text.length < 16) return null;
1127
1341
  const probe = {
1128
1342
  role: "assistant",
@@ -1137,9 +1351,8 @@ function isInternalTraceMessage(message) {
1137
1351
  if (!content) return true;
1138
1352
  return TECHNICAL_TRACE_RE.test(content) || TECHNICAL_SELECTOR_RE.test(content);
1139
1353
  }
1140
- var CREATE_FORM_ROUTE_RE = /vehiclecreate|subscriptioncreate|vehicleedit|subscriptionedit|maintenancecreate|create|cadastr|register|novo|edit/i;
1141
- var SUBMIT_BUTTON_RE = /cadastrar|registrar|salvar|submit|register|guardar|enviar|confirmar|save/i;
1142
- var FORM_VALIDATION_TEXT_RE = /(inv[aá]lid|obrigat[oó]ri|required|invalid|j[aá]\s+existe|already\s+exists|duplicad|formato|format|too\s+short|muito\s+curto|n[aã]o\s+(?:foi|pode)|must\s+be|deve\s+ser|selecione|escolha|select\s+(?:the|a|o|an))/i;
1354
+ var CREATE_FORM_ROUTE_RE = CREATE_EDIT_ROUTE_RE;
1355
+ var SUBMIT_BUTTON_RE = SUBMIT_CONTROL_RE;
1143
1356
  function normalize2(value) {
1144
1357
  return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
1145
1358
  }
@@ -1157,7 +1370,9 @@ function snapshotHasVisibleSubmitButton(snap) {
1157
1370
  );
1158
1371
  }
1159
1372
  function snapshotHasVisibleValidation(snap) {
1160
- return (snap.texts ?? []).some((text) => typeof text === "string" && FORM_VALIDATION_TEXT_RE.test(text));
1373
+ return (snap.texts ?? []).some(
1374
+ (text) => typeof text === "string" && FORM_VALIDATION_TEXT_RE.test(text)
1375
+ );
1161
1376
  }
1162
1377
  function snapshotShowsOpenCreateForm(snap, activePath = []) {
1163
1378
  if (!snap) return false;
@@ -1574,9 +1789,10 @@ var ChatSessionMachine = class {
1574
1789
  };
1575
1790
  }
1576
1791
  const hasToolWithoutEffect = toolWithoutEffectIds.has(actionId);
1792
+ const observedNoEffect = hasToolWithoutEffect || r.effect === "none";
1577
1793
  let recoveryAttempt = 0;
1578
1794
  let recoveryExhausted = false;
1579
- if (!r.success || hasToolWithoutEffect) {
1795
+ if (!r.skipped && (!r.success || observedNoEffect)) {
1580
1796
  const previous = this.recoveryCountByFingerprint.get(r.fingerprint) ?? 0;
1581
1797
  const next = previous + 1;
1582
1798
  this.recoveryCountByFingerprint.set(r.fingerprint, next);
@@ -1593,16 +1809,18 @@ var ChatSessionMachine = class {
1593
1809
  recoveryAttempt,
1594
1810
  recoveryExhausted,
1595
1811
  toolWithoutEffect: hasToolWithoutEffect,
1596
- effect: hasToolWithoutEffect ? "none" : r.effect,
1812
+ effect: observedNoEffect ? "none" : r.effect,
1597
1813
  userVisibleStatusKey: r.userVisibleStatusKey
1598
1814
  };
1599
1815
  });
1600
1816
  const exhausted = results.filter((r) => r.recoveryExhausted);
1601
1817
  this.setState({
1602
- loadingStatusKey: results.some((r) => !r.success || r.toolWithoutEffect) ? "statusAdjusting" : "statusAnalyzing"
1818
+ loadingStatusKey: results.some(
1819
+ (r) => !r.success || r.toolWithoutEffect || r.effect === "none"
1820
+ ) ? "statusAdjusting" : "statusAnalyzing"
1603
1821
  });
1604
- console.log(
1605
- `[Appilots] ChatSessionMachine: running continuation hop=${turn.hop} (${results.filter((r) => r.success).length}/${results.length} succeeded` + (exhausted.length > 0 ? `, ${exhausted.length} recovery-exhausted` : "") + ")"
1822
+ appilotsDebugLog(
1823
+ `ChatSessionMachine: running continuation hop=${turn.hop} (${results.filter((r) => r.success).length}/${results.length} succeeded` + (exhausted.length > 0 ? `, ${exhausted.length} recovery-exhausted` : "") + ")"
1606
1824
  );
1607
1825
  const response = await this.deps.client.continueAgent(results, context, turn.hop);
1608
1826
  this.addAssistantMessage(response.message);
@@ -1675,6 +1893,7 @@ var ChatSessionMachine = class {
1675
1893
  const diagnose = event.data?.diagnose;
1676
1894
  const effect = event.data?.effect;
1677
1895
  const userVisibleStatusKey = event.data?.userVisibleStatusKey;
1896
+ const skipped = event.data?.skipped === true;
1678
1897
  const existing = turn.results.get(actionId);
1679
1898
  if (existing) {
1680
1899
  existing.success = success;
@@ -1682,6 +1901,7 @@ var ChatSessionMachine = class {
1682
1901
  existing.diagnose = diagnose;
1683
1902
  existing.effect = effect;
1684
1903
  existing.userVisibleStatusKey = userVisibleStatusKey;
1904
+ existing.skipped = skipped;
1685
1905
  }
1686
1906
  turn.reportedIds.add(actionId);
1687
1907
  if (turn.reportedIds.size >= turn.actionIds.size) {
@@ -1718,8 +1938,8 @@ var ChatSessionMachine = class {
1718
1938
  this.abortController = abortController;
1719
1939
  try {
1720
1940
  const context = this.deps.adapter.buildContext();
1721
- console.log(
1722
- `[Appilots] ChatSessionMachine sendMessage: context \u2014 currentScreen="${context.currentScreen ?? "unknown"}", snapshot={texts:${context.snapshot?.texts?.length ?? 0}, inputs:${context.snapshot?.inputs?.length ?? 0}, buttons:${context.snapshot?.buttons?.length ?? 0}, elements:${context.snapshot?.elements?.length ?? 0}, lists:${context.snapshot?.lists?.length ?? 0}, listItems:${(context.snapshot?.lists ?? []).reduce((acc, l) => acc + (l.items?.length ?? 0), 0)}, choiceGroups:${context.snapshot?.choiceGroups?.length ?? 0}}`
1941
+ appilotsDebugLog(
1942
+ `ChatSessionMachine sendMessage: context \u2014 currentScreen="${context.currentScreen ?? "unknown"}", snapshot={texts:${context.snapshot?.texts?.length ?? 0}, inputs:${context.snapshot?.inputs?.length ?? 0}, buttons:${context.snapshot?.buttons?.length ?? 0}, elements:${context.snapshot?.elements?.length ?? 0}, lists:${context.snapshot?.lists?.length ?? 0}, listItems:${(context.snapshot?.lists ?? []).reduce((acc, l) => acc + (l.items?.length ?? 0), 0)}, choiceGroups:${context.snapshot?.choiceGroups?.length ?? 0}}`
1723
1943
  );
1724
1944
  let response;
1725
1945
  if (streamingEnabled) {
@@ -1747,9 +1967,7 @@ var ChatSessionMachine = class {
1747
1967
  });
1748
1968
  } catch (err) {
1749
1969
  if (err instanceof StreamTransportError) {
1750
- console.warn(
1751
- `[Appilots] streaming unavailable, falling back to JSON: ${err.message}`
1752
- );
1970
+ appilotsDebugWarn(`streaming unavailable, falling back to JSON: ${err.message}`);
1753
1971
  response = await this.deps.client.sendMessage(content.trim(), context);
1754
1972
  } else {
1755
1973
  throw err;
@@ -1758,8 +1976,8 @@ var ChatSessionMachine = class {
1758
1976
  } else {
1759
1977
  response = await this.deps.client.sendMessage(content.trim(), context);
1760
1978
  }
1761
- console.log(
1762
- `[Appilots] ChatSessionMachine sendMessage: response received \u2014 actions=${response.actions?.length ?? 0}, hasSummary=${!!response.summaryMessage}`
1979
+ appilotsDebugLog(
1980
+ `ChatSessionMachine sendMessage: response received \u2014 actions=${response.actions?.length ?? 0}, hasSummary=${!!response.summaryMessage}`
1763
1981
  );
1764
1982
  removeStreamingPlaceholder();
1765
1983
  this.addAssistantMessage(response.message);
@@ -1849,7 +2067,11 @@ function isStructuredRisky(value) {
1849
2067
  }
1850
2068
  function actionMetadataRequiresConfirmation(meta) {
1851
2069
  if (meta.requiresConfirmation === true || meta.destructive === true || meta.effect === "destructive" || meta.riskLevel === "high" || meta.risk === "destructive") {
1852
- return { required: true, label: typeof meta.label === "string" ? meta.label : void 0, source: "screen-metadata" };
2070
+ return {
2071
+ required: true,
2072
+ label: typeof meta.label === "string" ? meta.label : void 0,
2073
+ source: "screen-metadata"
2074
+ };
1853
2075
  }
1854
2076
  return { required: false };
1855
2077
  }
@@ -1890,13 +2112,9 @@ function toDestructiveConfirmPayload(confirm, policy, fallbackLabel) {
1890
2112
  const labelText = policy.label ?? existingDescription ?? fallbackLabel ?? "esta a\xE7\xE3o";
1891
2113
  return {
1892
2114
  ...payload,
1893
- title: "A\xE7\xE3o destrutiva",
1894
- message: `Tem certeza que deseja "${labelText}"?`,
1895
2115
  actionDescription: labelText,
1896
2116
  severity: "danger",
1897
- destructive: true,
1898
- confirmLabel: typeof payload.confirmLabel === "string" && payload.confirmLabel ? payload.confirmLabel : "Sim, executar",
1899
- cancelLabel: typeof payload.cancelLabel === "string" && payload.cancelLabel ? payload.cancelLabel : "Cancelar"
2117
+ destructive: true
1900
2118
  };
1901
2119
  }
1902
2120
  function confirmNeedsDestructiveUpgrade(confirm) {
@@ -1962,8 +2180,8 @@ var ActionQueueMachine = class {
1962
2180
  if (event.type !== "agent:action:start") return;
1963
2181
  const action = event.data?.action;
1964
2182
  if (!action?.id) return;
1965
- console.log(
1966
- `[Appilots] ActionQueueMachine: received agent:action:start event, action id="${action.id}", type="${action.type}"`
2183
+ appilotsDebugLog(
2184
+ `ActionQueueMachine: received agent:action:start event, action id="${action.id}", type="${action.type}"`
1967
2185
  );
1968
2186
  if (this.state.actions.some((a) => a.id === action.id)) return;
1969
2187
  this.setActions([...this.state.actions, action]);
@@ -2064,8 +2282,8 @@ var ActionQueueMachine = class {
2064
2282
  if (!policy.required) return false;
2065
2283
  const confirmId = `confirm-local-${target.id}`;
2066
2284
  if (this.state.actions.some((a) => a.id === confirmId)) return true;
2067
- console.log(
2068
- `[Appilots] ActionQueueMachine: action "${componentId || target.id}" requires confirmation (${policy.source ?? "local-policy"}) \u2014 synthesising local confirm (id="${confirmId}")`
2285
+ appilotsDebugLog(
2286
+ `ActionQueueMachine: action "${componentId || target.id}" requires confirmation (${policy.source ?? "local-policy"}) \u2014 synthesising local confirm (id="${confirmId}")`
2069
2287
  );
2070
2288
  const synth = {
2071
2289
  id: confirmId,
@@ -2111,13 +2329,13 @@ var ActionQueueMachine = class {
2111
2329
  const pending = this.state.actions.filter(
2112
2330
  (a) => a.status === "pending" && !this.processed.has(a.id)
2113
2331
  );
2114
- console.log(
2115
- `[Appilots] ActionQueueMachine: autoExecute check \u2014 ${pending.length} pending action(s) to process`
2332
+ appilotsDebugLog(
2333
+ `ActionQueueMachine: autoExecute check \u2014 ${pending.length} pending action(s) to process`
2116
2334
  );
2117
2335
  let lastWasNavigate = false;
2118
2336
  let preNavScreen = null;
2119
2337
  let preNavSignature = null;
2120
- for (const action of pending) {
2338
+ for (const [index, action] of pending.entries()) {
2121
2339
  if (action.type === "confirm") {
2122
2340
  continue;
2123
2341
  }
@@ -2127,14 +2345,14 @@ var ActionQueueMachine = class {
2127
2345
  const gate = findGatingConfirm(action, this.state.actions);
2128
2346
  if (gate) {
2129
2347
  if (gate.status === "pending" || gate.status === "executing") {
2130
- console.log(
2131
- `[Appilots] ActionQueueMachine: action id="${action.id}" is gated by confirm id="${gate.id}" (status=${gate.status}); waiting for user decision`
2348
+ appilotsDebugLog(
2349
+ `ActionQueueMachine: action id="${action.id}" is gated by confirm id="${gate.id}" (status=${gate.status}); waiting for user decision`
2132
2350
  );
2133
2351
  continue;
2134
2352
  }
2135
2353
  if (gate.status === "failed") {
2136
- console.log(
2137
- `[Appilots] ActionQueueMachine: confirm id="${gate.id}" was rejected; skipping gated action id="${action.id}"`
2354
+ appilotsDebugLog(
2355
+ `ActionQueueMachine: confirm id="${gate.id}" was rejected; skipping gated action id="${action.id}"`
2138
2356
  );
2139
2357
  this.processed.add(action.id);
2140
2358
  this.updateActions(
@@ -2166,12 +2384,12 @@ var ActionQueueMachine = class {
2166
2384
  fromSignature: preNavSignature,
2167
2385
  maxMs: 3500
2168
2386
  });
2169
- console.log(
2170
- `[Appilots] ActionQueueMachine: post-navigate settle done \u2014 screen="${settle.screen}" transitioned=${settle.transitioned} timedOut=${settle.timedOut} waitedMs=${settle.waitedMs}`
2387
+ appilotsDebugLog(
2388
+ `ActionQueueMachine: post-navigate settle done \u2014 screen="${settle.screen}" transitioned=${settle.transitioned} timedOut=${settle.timedOut} waitedMs=${settle.waitedMs}`
2171
2389
  );
2172
2390
  }
2173
- console.log(
2174
- `[Appilots] ActionQueueMachine: auto-executing action id="${action.id}" type="${action.type}"`
2391
+ appilotsDebugLog(
2392
+ `ActionQueueMachine: auto-executing action id="${action.id}" type="${action.type}"`
2175
2393
  );
2176
2394
  this.processed.add(action.id);
2177
2395
  if (action.type === "navigate") {
@@ -2180,20 +2398,67 @@ var ActionQueueMachine = class {
2180
2398
  }
2181
2399
  const succeeded = await this.approveAction(action.id);
2182
2400
  if (!succeeded) {
2401
+ const failed = this.state.actions.find((candidate) => candidate.id === action.id);
2183
2402
  this.warn(
2184
2403
  `ActionQueueMachine: stopping current action batch after failed action id="${action.id}"`
2185
2404
  );
2405
+ await this.skipRemainingBatchActions(pending.slice(index + 1), action, failed?.error);
2186
2406
  break;
2187
2407
  }
2188
2408
  lastWasNavigate = action.type === "navigate";
2189
2409
  }
2190
2410
  }
2411
+ /**
2412
+ * A turn is an all-terminal contract: ChatSessionMachine cannot ask the
2413
+ * server for the next step until every action in the current batch has
2414
+ * reported a completion or an error. When an early action fails we must
2415
+ * not execute dependent actions, but leaving them `pending` deadlocks the
2416
+ * whole turn. Mark the untouched suffix as failed/skipped and emit the
2417
+ * same terminal event a real executor would have emitted.
2418
+ */
2419
+ async skipRemainingBatchActions(remaining, failedAction, failureReason) {
2420
+ const skipped = remaining.filter((candidate) => {
2421
+ const current = this.state.actions.find((action) => action.id === candidate.id);
2422
+ if (current?.status !== "pending" || this.processed.has(candidate.id)) return false;
2423
+ if (candidate.type === "confirm") return false;
2424
+ const gate = findGatingConfirm(candidate, this.state.actions);
2425
+ if (gate && (gate.status === "pending" || gate.status === "executing")) return false;
2426
+ return true;
2427
+ });
2428
+ if (skipped.length === 0) return;
2429
+ const reason = `Skipped because previous action "${failedAction.id}" failed` + (failureReason ? `: ${failureReason}` : "");
2430
+ const skippedIds = new Set(skipped.map((action) => action.id));
2431
+ for (const action of skipped) this.processed.add(action.id);
2432
+ this.updateActions(
2433
+ (prev) => prev.map(
2434
+ (action) => skippedIds.has(action.id) ? { ...action, status: "failed", error: reason } : action
2435
+ )
2436
+ );
2437
+ for (const action of skipped) {
2438
+ try {
2439
+ await this.deps.client.completeAction(action.id, false, reason);
2440
+ } catch {
2441
+ }
2442
+ this.deps.emit({
2443
+ type: "agent:action:error",
2444
+ timestamp: Date.now(),
2445
+ data: {
2446
+ actionId: action.id,
2447
+ actionType: action.type,
2448
+ success: false,
2449
+ error: reason,
2450
+ skipped: true,
2451
+ blockedByActionId: failedAction.id
2452
+ }
2453
+ });
2454
+ }
2455
+ }
2191
2456
  // ── Approve & execute an action ─────────────────────────────
2192
2457
  async approveAction(actionId) {
2193
2458
  const action = this.state.actions.find((a) => a.id === actionId);
2194
2459
  if (!action) return false;
2195
- console.log(
2196
- `[Appilots] ActionQueueMachine: action status transition: pending \u2192 executing (id="${actionId}", type="${action.type}")`
2460
+ appilotsDebugLog(
2461
+ `ActionQueueMachine: action status transition: pending \u2192 executing (id="${actionId}", type="${action.type}")`
2197
2462
  );
2198
2463
  this.updateActions(
2199
2464
  (prev) => prev.map((a) => a.id === actionId ? { ...a, status: "executing" } : a)
@@ -2205,8 +2470,8 @@ var ActionQueueMachine = class {
2205
2470
  { confirmedDestructive }
2206
2471
  );
2207
2472
  if (result.success) {
2208
- console.log(
2209
- `[Appilots] ActionQueueMachine: action status transition: executing \u2192 completed (id="${actionId}", type="${action.type}")`
2473
+ appilotsDebugLog(
2474
+ `ActionQueueMachine: action status transition: executing \u2192 completed (id="${actionId}", type="${action.type}")`
2210
2475
  );
2211
2476
  this.updateActions(
2212
2477
  (prev) => prev.map((a) => a.id === actionId ? { ...a, status: "completed" } : a)
@@ -2285,9 +2550,7 @@ var ActionQueueMachine = class {
2285
2550
  return this.state.actions.filter((a) => a.status === "executing");
2286
2551
  }
2287
2552
  get completedActions() {
2288
- return this.state.actions.filter(
2289
- (a) => a.status === "completed" || a.status === "failed"
2290
- );
2553
+ return this.state.actions.filter((a) => a.status === "completed" || a.status === "failed");
2291
2554
  }
2292
2555
  };
2293
2556
  function normalize3(s) {
@@ -2316,13 +2579,11 @@ function normalizeId(value) {
2316
2579
  function isOptionalCosmeticTargetId(targetId) {
2317
2580
  if (typeof targetId !== "string" || !targetId.trim()) return false;
2318
2581
  const id = normalizeId(targetId);
2319
- return /foto|photo|avatar|icone|icon|emoji|sheetaction|sheet-action|btn-foto/.test(id) || id.includes("photo-sheet");
2582
+ return /sheetaction|sheet-action|photo-sheet|actionsheet|action-sheet/.test(id);
2320
2583
  }
2321
2584
  function routeLooksLikeFormContext(route) {
2322
2585
  const haystack = normalizeId(route ?? "");
2323
- return /create|edit|form|cadastr|register|novo|vehiclecreate|subscriptioncreate|vehicleedit|subscriptioncreate/.test(
2324
- haystack
2325
- );
2586
+ return /create|edit|form|cadastr|criar|register|registr|novo|nova/.test(haystack);
2326
2587
  }
2327
2588
  function screenPathParts(screen) {
2328
2589
  if (!screen) return [];
@@ -2697,6 +2958,7 @@ function listItemElement(route, list, item) {
2697
2958
  list.id ?? list.label ?? `list-${list.index}`,
2698
2959
  key
2699
2960
  ]);
2961
+ const provenance = item.itemKey || item.reactKey ? "declared" : label ? "derived" : "positional";
2700
2962
  const inModal = (item.buttons ?? []).some((button) => button.inModal) || (item.inputs ?? []).some((input) => input.inModal) || (item.toggles ?? []).some((toggle) => toggle.inModal);
2701
2963
  return {
2702
2964
  id,
@@ -2706,6 +2968,7 @@ function listItemElement(route, list, item) {
2706
2968
  actions: ["press"],
2707
2969
  disabled: false,
2708
2970
  source: "list",
2971
+ provenance,
2709
2972
  targetId: item.syntheticId,
2710
2973
  ...inModal ? { inModal: true } : {},
2711
2974
  listContext: {
@@ -2735,17 +2998,14 @@ function buttonElement(route, button, index) {
2735
2998
  disabled: button.disabled,
2736
2999
  selected: button.selected,
2737
3000
  source: "button",
3001
+ ...button.provenance ? { provenance: button.provenance } : {},
2738
3002
  targetId: button.id ?? button.label,
2739
3003
  ...button.inModal ? { inModal: true } : {}
2740
3004
  };
2741
3005
  }
2742
3006
  function inputElement(route, input, index) {
2743
3007
  const label = input.label ?? input.placeholder ?? input.id;
2744
- const id = stableElementId([
2745
- "input",
2746
- route ?? "unknown",
2747
- input.id ?? label ?? `input-${index}`
2748
- ]);
3008
+ const id = stableElementId(["input", route ?? "unknown", input.id ?? label ?? `input-${index}`]);
2749
3009
  return {
2750
3010
  id,
2751
3011
  role: "input",
@@ -2754,6 +3014,7 @@ function inputElement(route, input, index) {
2754
3014
  actions: ["focus", "setValue"],
2755
3015
  disabled: input.editable === false,
2756
3016
  source: "input",
3017
+ ...input.provenance ? { provenance: input.provenance } : {},
2757
3018
  targetId: input.id ?? input.label ?? input.placeholder,
2758
3019
  ...input.inModal ? { inModal: true } : {}
2759
3020
  };
@@ -2773,10 +3034,45 @@ function toggleElement(route, toggle, index) {
2773
3034
  actions: ["toggle", "press"],
2774
3035
  selected: toggle.value,
2775
3036
  source: "toggle",
3037
+ ...toggle.provenance ? { provenance: toggle.provenance } : {},
2776
3038
  targetId: toggle.id ?? toggle.label,
2777
3039
  ...toggle.inModal ? { inModal: true } : {}
2778
3040
  };
2779
3041
  }
3042
+ function listItemToggleElement(route, list, item, toggle, toggleIndex) {
3043
+ const rowLabel = labelForListItem(item);
3044
+ const label = toggle.label ?? rowLabel ?? toggle.id;
3045
+ const key = item.itemKey ?? item.reactKey ?? rowLabel ?? item.syntheticId;
3046
+ const id = stableElementId([
3047
+ "toggle",
3048
+ route ?? "unknown",
3049
+ list.id ?? list.label ?? `list-${list.index}`,
3050
+ key,
3051
+ toggle.id ?? `toggle-${toggleIndex}`
3052
+ ]);
3053
+ return {
3054
+ id,
3055
+ role: "toggle",
3056
+ label,
3057
+ // The row's texts are what the user actually says out loud.
3058
+ texts: uniqueTexts([label, ...item.texts ?? []]),
3059
+ actions: ["toggle", "press"],
3060
+ selected: toggle.value,
3061
+ source: "list",
3062
+ ...toggle.provenance ? { provenance: toggle.provenance } : {},
3063
+ targetId: toggle.id,
3064
+ listContext: {
3065
+ listIndex: list.index,
3066
+ listId: list.id,
3067
+ listLabel: list.label,
3068
+ itemIndex: item.index,
3069
+ itemKey: item.itemKey,
3070
+ reactKey: item.reactKey,
3071
+ syntheticId: item.syntheticId
3072
+ },
3073
+ ...toggle.inModal ? { inModal: true } : {}
3074
+ };
3075
+ }
2780
3076
  function sliderElement(route, slider, index) {
2781
3077
  const label = slider.label ?? slider.id;
2782
3078
  const id = stableElementId([
@@ -2792,37 +3088,48 @@ function sliderElement(route, slider, index) {
2792
3088
  actions: ["setValue"],
2793
3089
  disabled: slider.disabled,
2794
3090
  source: "slider",
3091
+ ...slider.provenance ? { provenance: slider.provenance } : {},
2795
3092
  targetId: slider.id ?? slider.label,
2796
3093
  ...slider.inModal ? { inModal: true } : {}
2797
3094
  };
2798
3095
  }
2799
- function deriveInteractionElements(snapshot) {
3096
+ function deriveInteractionElements(snapshot, options) {
2800
3097
  const elements = [];
2801
3098
  const seen = /* @__PURE__ */ new Set();
2802
- const push = (element) => {
3099
+ const rectOf = options?.rectOf;
3100
+ const push = (element, node) => {
2803
3101
  if (!element.id || seen.has(element.id)) return;
2804
3102
  seen.add(element.id);
2805
- elements.push(element);
3103
+ const rect = rectOf && node !== void 0 ? rectOf(node) : void 0;
3104
+ elements.push(rect ? { ...element, rect } : element);
2806
3105
  };
2807
3106
  for (const list of snapshot.lists ?? []) {
2808
3107
  for (const item of list.items ?? []) {
2809
- push(listItemElement(snapshot.route, list, item));
3108
+ push(listItemElement(snapshot.route, list, item), item);
3109
+ for (const [toggleIndex, toggle] of (item.toggles ?? []).entries()) {
3110
+ push(listItemToggleElement(snapshot.route, list, item, toggle, toggleIndex), toggle);
3111
+ }
2810
3112
  }
2811
3113
  }
2812
3114
  for (const [index, button] of (snapshot.buttons ?? []).entries()) {
2813
- push(buttonElement(snapshot.route, button, index));
3115
+ push(buttonElement(snapshot.route, button, index), button);
2814
3116
  }
2815
3117
  for (const [index, input] of (snapshot.inputs ?? []).entries()) {
2816
- push(inputElement(snapshot.route, input, index));
3118
+ push(inputElement(snapshot.route, input, index), input);
2817
3119
  }
2818
3120
  for (const [index, toggle] of (snapshot.toggles ?? []).entries()) {
2819
- push(toggleElement(snapshot.route, toggle, index));
3121
+ push(toggleElement(snapshot.route, toggle, index), toggle);
2820
3122
  }
2821
3123
  for (const [index, slider] of (snapshot.sliders ?? []).entries()) {
2822
- push(sliderElement(snapshot.route, slider, index));
3124
+ push(sliderElement(snapshot.route, slider, index), slider);
2823
3125
  }
2824
3126
  return elements;
2825
3127
  }
3128
+ function toWireElement(entry) {
3129
+ if (entry.rect === void 0) return entry;
3130
+ const { rect: _rect, ...wire } = entry;
3131
+ return wire;
3132
+ }
2826
3133
  function findElementForChoice(elements, option) {
2827
3134
  if (option.targetId) {
2828
3135
  const byTarget = elements.find((element) => element.targetId === option.targetId);
@@ -2863,6 +3170,7 @@ var WIRE_LIMITS = {
2863
3170
  listItems: 500,
2864
3171
  listDataPreview: 500,
2865
3172
  listDataPreviewTextLength: 500,
3173
+ scrollables: 20,
2866
3174
  choiceGroups: 100,
2867
3175
  choiceOptions: 200,
2868
3176
  elements: 1e3,
@@ -2943,6 +3251,9 @@ function clampSnapshotToWireLimits(snapshot) {
2943
3251
  snapshot.lists = (cut.array(snapshot.lists, WIRE_LIMITS.lists) ?? []).map(
2944
3252
  (list) => clampList(cut, list)
2945
3253
  );
3254
+ if (snapshot.scrollables) {
3255
+ snapshot.scrollables = cut.array(snapshot.scrollables, WIRE_LIMITS.scrollables);
3256
+ }
2946
3257
  snapshot.choiceGroups = (cut.array(snapshot.choiceGroups, WIRE_LIMITS.choiceGroups) ?? []).map(
2947
3258
  (group) => clampChoiceGroup(cut, group)
2948
3259
  );
@@ -2970,7 +3281,9 @@ var ComponentRegistryImpl = class {
2970
3281
  */
2971
3282
  register(id, entry) {
2972
3283
  this.components.set(id, entry);
2973
- console.log(`[Appilots] ComponentRegistry.register: id="${id}" kind="${entry.kind}" \u2014 total count: ${this.components.size}`);
3284
+ appilotsDebugLog(
3285
+ `ComponentRegistry.register: id="${id}" kind="${entry.kind}" \u2014 total count: ${this.components.size}`
3286
+ );
2974
3287
  this.notify(id, entry);
2975
3288
  }
2976
3289
  /**
@@ -2978,7 +3291,9 @@ var ComponentRegistryImpl = class {
2978
3291
  */
2979
3292
  unregister(id) {
2980
3293
  this.components.delete(id);
2981
- console.log(`[Appilots] ComponentRegistry.unregister: id="${id}" \u2014 total count: ${this.components.size}`);
3294
+ appilotsDebugLog(
3295
+ `ComponentRegistry.unregister: id="${id}" \u2014 total count: ${this.components.size}`
3296
+ );
2982
3297
  this.notify(id, null);
2983
3298
  }
2984
3299
  /**
@@ -3082,14 +3397,14 @@ var ListRegistryImpl = class {
3082
3397
  listeners = /* @__PURE__ */ new Set();
3083
3398
  register(id, entry) {
3084
3399
  this.lists.set(id, entry);
3085
- console.log(
3086
- `[Appilots] ListRegistry.register: id="${id}" component="${entry.component}" items=${entry.itemCount ?? "unknown"} \u2014 total count: ${this.lists.size}`
3400
+ appilotsDebugLog(
3401
+ `ListRegistry.register: id="${id}" component="${entry.component}" items=${entry.itemCount ?? "unknown"} \u2014 total count: ${this.lists.size}`
3087
3402
  );
3088
3403
  this.notify(id, entry);
3089
3404
  }
3090
3405
  unregister(id) {
3091
3406
  this.lists.delete(id);
3092
- console.log(`[Appilots] ListRegistry.unregister: id="${id}" \u2014 total count: ${this.lists.size}`);
3407
+ appilotsDebugLog(`ListRegistry.unregister: id="${id}" \u2014 total count: ${this.lists.size}`);
3093
3408
  this.notify(id, null);
3094
3409
  }
3095
3410
  get(id) {
@@ -3135,7 +3450,7 @@ var ElementRegistryImpl = class {
3135
3450
  next.set(element.id, element);
3136
3451
  }
3137
3452
  this.elements = next;
3138
- console.log(`[Appilots] ElementRegistry.replaceAll: elements=${this.elements.size}`);
3453
+ appilotsDebugLog(`ElementRegistry.replaceAll: elements=${this.elements.size}`);
3139
3454
  }
3140
3455
  register(id, entry) {
3141
3456
  this.elements.set(id, entry);
@@ -3276,20 +3591,20 @@ function extractId(props) {
3276
3591
  if (raw && typeof raw === "string") {
3277
3592
  const normalized = normalizeId2(raw);
3278
3593
  if (normalized && normalized.length > 0) {
3279
- console.log(`[Appilots] extractId: raw="${raw}" \u2192 normalized="${normalized}"`);
3594
+ appilotsDebugLog(`extractId: raw="${raw}" \u2192 normalized="${normalized}"`);
3280
3595
  return normalized;
3281
3596
  }
3282
- console.log(`[Appilots] extractId: raw="${raw}" normalized to empty string, falling back`);
3597
+ appilotsDebugLog(`extractId: raw="${raw}" normalized to empty string, falling back`);
3283
3598
  }
3284
3599
  const hint = deriveSemanticInputId(props);
3285
3600
  if (hint) {
3286
- console.warn(
3287
- `[Appilots] extractId: no testID/nativeID/accessibilityLabel/name/placeholder/autoComplete on input \u2014 falling back to semantic hint "${hint}". Add testID to make this field reliably addressable.`
3601
+ appilotsDebugWarn(
3602
+ `extractId: no testID/nativeID/accessibilityLabel/name/placeholder/autoComplete on input \u2014 falling back to semantic hint "${hint}". Add testID to make this field reliably addressable.`
3288
3603
  );
3289
3604
  return hint;
3290
3605
  }
3291
- console.log(
3292
- "[Appilots] extractId: no identifiable prop found (testID/nativeID/accessibilityLabel/name/placeholder/autoComplete missing); skipping registration"
3606
+ appilotsDebugLog(
3607
+ "extractId: no identifiable prop found (testID/nativeID/accessibilityLabel/name/placeholder/autoComplete missing); skipping registration"
3293
3608
  );
3294
3609
  return null;
3295
3610
  }
@@ -3307,15 +3622,17 @@ var TrackedTextInput = React2__default.default.forwardRef(function TrackedTextIn
3307
3622
  onChangeRef.current = props.onChangeText;
3308
3623
  React2__default.default.useEffect(() => {
3309
3624
  if (!id) {
3310
- console.log("[Appilots] TrackedTextInput useEffect: no id extracted, skipping registration");
3625
+ appilotsDebugLog("TrackedTextInput useEffect: no id extracted, skipping registration");
3311
3626
  return;
3312
3627
  }
3313
- console.log(`[Appilots] TrackedTextInput useEffect: registering field id="${id}" label="${extractLabel(props)}"`);
3628
+ appilotsDebugLog(
3629
+ `TrackedTextInput useEffect: registering field id="${id}" label="${extractLabel(props)}"`
3630
+ );
3314
3631
  const entry = {
3315
3632
  kind: "field",
3316
3633
  getValue: () => valueRef.current,
3317
3634
  setValue: (v) => {
3318
- console.log(`[Appilots] TrackedTextInput setValue called: id="${id}" value="${v}"`);
3635
+ appilotsDebugLog(`TrackedTextInput setValue: id="${id}" value=${describeValue(v)}`);
3319
3636
  onChangeRef.current?.(v);
3320
3637
  inputRef.current?.setNativeProps?.({ text: v });
3321
3638
  },
@@ -3325,15 +3642,18 @@ var TrackedTextInput = React2__default.default.forwardRef(function TrackedTextIn
3325
3642
  };
3326
3643
  registry.register(id, entry);
3327
3644
  return () => {
3328
- console.log(`[Appilots] TrackedTextInput useEffect cleanup: unregistering field id="${id}"`);
3645
+ appilotsDebugLog(`TrackedTextInput useEffect cleanup: unregistering field id="${id}"`);
3329
3646
  registry.unregister(id);
3330
3647
  };
3331
3648
  }, [id]);
3332
- const mergedRef = React2__default.default.useCallback((instance) => {
3333
- inputRef.current = instance;
3334
- if (typeof forwardedRef === "function") forwardedRef(instance);
3335
- else if (forwardedRef && typeof forwardedRef === "object") forwardedRef.current = instance;
3336
- }, [forwardedRef]);
3649
+ const mergedRef = React2__default.default.useCallback(
3650
+ (instance) => {
3651
+ inputRef.current = instance;
3652
+ if (typeof forwardedRef === "function") forwardedRef(instance);
3653
+ else if (forwardedRef && typeof forwardedRef === "object") forwardedRef.current = instance;
3654
+ },
3655
+ [forwardedRef]
3656
+ );
3337
3657
  return _originalCreateElement(reactNative.TextInput, { ...props, ref: mergedRef });
3338
3658
  });
3339
3659
  var TrackedSwitch = React2__default.default.forwardRef(function TrackedSwitch2(props, forwardedRef) {
@@ -3345,22 +3665,24 @@ var TrackedSwitch = React2__default.default.forwardRef(function TrackedSwitch2(p
3345
3665
  onChangeRef.current = props.onValueChange;
3346
3666
  React2__default.default.useEffect(() => {
3347
3667
  if (!id) {
3348
- console.log("[Appilots] TrackedSwitch useEffect: no id extracted, skipping registration");
3668
+ appilotsDebugLog("TrackedSwitch useEffect: no id extracted, skipping registration");
3349
3669
  return;
3350
3670
  }
3351
- console.log(`[Appilots] TrackedSwitch useEffect: registering toggle id="${id}" label="${extractLabel(props)}"`);
3671
+ appilotsDebugLog(
3672
+ `TrackedSwitch useEffect: registering toggle id="${id}" label="${extractLabel(props)}"`
3673
+ );
3352
3674
  const entry = {
3353
3675
  kind: "toggle",
3354
3676
  getValue: () => valueRef.current,
3355
3677
  setValue: (v) => {
3356
- console.log(`[Appilots] TrackedSwitch setValue called: id="${id}" value=${v}`);
3678
+ appilotsDebugLog(`TrackedSwitch setValue called: id="${id}" value=${v}`);
3357
3679
  onChangeRef.current?.(v);
3358
3680
  },
3359
3681
  label: extractLabel(props)
3360
3682
  };
3361
3683
  registry.register(id, entry);
3362
3684
  return () => {
3363
- console.log(`[Appilots] TrackedSwitch useEffect cleanup: unregistering toggle id="${id}"`);
3685
+ appilotsDebugLog(`TrackedSwitch useEffect cleanup: unregistering toggle id="${id}"`);
3364
3686
  registry.unregister(id);
3365
3687
  };
3366
3688
  }, [id]);
@@ -3380,21 +3702,25 @@ function getTrackedPressable(OriginalComponent) {
3380
3702
  return;
3381
3703
  }
3382
3704
  if (!id || !props.onPress) {
3383
- console.log(`[Appilots] TrackedPressable useEffect: skipping registration (id=${id}, hasOnPress=${!!props.onPress})`);
3705
+ appilotsDebugLog(
3706
+ `TrackedPressable useEffect: skipping registration (id=${id}, hasOnPress=${!!props.onPress})`
3707
+ );
3384
3708
  return;
3385
3709
  }
3386
- console.log(`[Appilots] TrackedPressable useEffect: registering target id="${id}" label="${extractLabel(props)}"`);
3710
+ appilotsDebugLog(
3711
+ `TrackedPressable useEffect: registering target id="${id}" label="${extractLabel(props)}"`
3712
+ );
3387
3713
  const entry = {
3388
3714
  kind: "target",
3389
3715
  press: () => {
3390
- console.log(`[Appilots] TrackedPressable press called: id="${id}"`);
3716
+ appilotsDebugLog(`TrackedPressable press called: id="${id}"`);
3391
3717
  onPressRef.current?.();
3392
3718
  },
3393
3719
  label: extractLabel(props)
3394
3720
  };
3395
3721
  registry.register(id, entry);
3396
3722
  return () => {
3397
- console.log(`[Appilots] TrackedPressable useEffect cleanup: unregistering target id="${id}"`);
3723
+ appilotsDebugLog(`TrackedPressable useEffect cleanup: unregistering target id="${id}"`);
3398
3724
  registry.unregister(id);
3399
3725
  };
3400
3726
  }, [id, !!props.onPress]);
@@ -3403,12 +3729,7 @@ function getTrackedPressable(OriginalComponent) {
3403
3729
  _pressableWrapperCache.set(OriginalComponent, cached);
3404
3730
  return cached;
3405
3731
  }
3406
- var LIST_COMPONENT_NAMES = /* @__PURE__ */ new Set([
3407
- "FlatList",
3408
- "SectionList",
3409
- "VirtualizedList",
3410
- "FlashList"
3411
- ]);
3732
+ var LIST_COMPONENT_NAMES = /* @__PURE__ */ new Set(["FlatList", "SectionList", "VirtualizedList", "FlashList"]);
3412
3733
  var _listWrapperCache = /* @__PURE__ */ new Map();
3413
3734
  var _generatedListIdCounter = 0;
3414
3735
  function componentDisplayName(type) {
@@ -3462,12 +3783,10 @@ var PREVIEW_TEXT_KEYS = [
3462
3783
  "label",
3463
3784
  "description",
3464
3785
  "descricao",
3465
- "plate",
3466
- "placa",
3467
- "model",
3468
- "modelo",
3469
3786
  "email",
3470
- "username"
3787
+ "username",
3788
+ "code",
3789
+ "codigo"
3471
3790
  ];
3472
3791
  function projectItemText(item) {
3473
3792
  if (item == null) return "";
@@ -3563,10 +3882,7 @@ function getTrackedList(OriginalComponent, componentName) {
3563
3882
  const itemCount = resolveItemCount(props);
3564
3883
  const refreshing = props.refreshing === true;
3565
3884
  const label = listLabel(props);
3566
- const dataPreview = React2__default.default.useMemo(
3567
- () => buildDataPreview(props),
3568
- [props.data, props.sections]
3569
- );
3885
+ const dataPreview = React2__default.default.useMemo(() => buildDataPreview(props), [props.data, props.sections]);
3570
3886
  const mergedRef = React2__default.default.useCallback(
3571
3887
  (instance) => {
3572
3888
  instanceRef.current = instance;
@@ -3601,7 +3917,7 @@ function getTrackedList(OriginalComponent, componentName) {
3601
3917
  }
3602
3918
  return false;
3603
3919
  } catch (err) {
3604
- console.warn(`[Appilots] TrackedList scrollToIndex failed for "${listId}":`, err);
3920
+ appilotsDebugWarn(`TrackedList scrollToIndex failed for "${listId}":`, err);
3605
3921
  return false;
3606
3922
  }
3607
3923
  },
@@ -3620,7 +3936,7 @@ function getTrackedList(OriginalComponent, componentName) {
3620
3936
  }
3621
3937
  return false;
3622
3938
  } catch (err) {
3623
- console.warn(`[Appilots] TrackedList scrollToOffset failed for "${listId}":`, err);
3939
+ appilotsDebugWarn(`TrackedList scrollToOffset failed for "${listId}":`, err);
3624
3940
  return false;
3625
3941
  }
3626
3942
  },
@@ -3664,26 +3980,148 @@ function getTrackedList(OriginalComponent, componentName) {
3664
3980
  _listWrapperCache.set(OriginalComponent, cached);
3665
3981
  return cached;
3666
3982
  }
3667
- var _enabled = false;
3983
+ var SCROLLABLE_COMPONENT_NAMES = /* @__PURE__ */ new Set(["ScrollView", "KeyboardAwareScrollView"]);
3984
+ var _scrollWrapperCache = /* @__PURE__ */ new Map();
3985
+ var _generatedScrollIdCounter = 0;
3986
+ function extractScrollId(props, componentName) {
3987
+ const raw = props?.appilotsScrollId ?? props?.testID ?? props?.accessibilityLabel;
3988
+ if (typeof raw === "string" && raw.trim().length > 0) return raw.trim();
3989
+ _generatedScrollIdCounter += 1;
3990
+ return `${componentName.toLowerCase()}-${_generatedScrollIdCounter}`;
3991
+ }
3992
+ function chain(appHandler, ours) {
3993
+ return ((...args) => {
3994
+ if (typeof appHandler === "function") {
3995
+ try {
3996
+ appHandler(...args);
3997
+ } catch (err) {
3998
+ appilotsDebugWarn("TrackedScrollView: app scroll handler threw:", err);
3999
+ }
4000
+ }
4001
+ ours(...args);
4002
+ });
4003
+ }
4004
+ function getTrackedScrollView(OriginalComponent, componentName) {
4005
+ let cached = _scrollWrapperCache.get(OriginalComponent);
4006
+ if (cached) return cached;
4007
+ cached = React2__default.default.forwardRef(function TrackedScrollView(props, forwardedRef) {
4008
+ const idRef = React2__default.default.useRef(null);
4009
+ if (!idRef.current) idRef.current = extractScrollId(props, componentName);
4010
+ const scrollId = idRef.current;
4011
+ const instanceRef = React2__default.default.useRef(null);
4012
+ const metricsRef = React2__default.default.useRef({
4013
+ offset: 0,
4014
+ visibleLength: 0,
4015
+ contentLength: 0
4016
+ });
4017
+ const horizontal = props.horizontal === true;
4018
+ const mergedRef = React2__default.default.useCallback(
4019
+ (instance) => {
4020
+ instanceRef.current = instance;
4021
+ if (typeof forwardedRef === "function") forwardedRef(instance);
4022
+ else if (forwardedRef && typeof forwardedRef === "object") forwardedRef.current = instance;
4023
+ },
4024
+ [forwardedRef]
4025
+ );
4026
+ const label = typeof props.appilotsScrollLabel === "string" ? props.appilotsScrollLabel : typeof props.accessibilityLabel === "string" ? props.accessibilityLabel : void 0;
4027
+ React2__default.default.useEffect(() => {
4028
+ listRegistry.register(scrollId, {
4029
+ kind: "scroll",
4030
+ id: scrollId,
4031
+ component: componentName,
4032
+ label,
4033
+ scrollToOffset: (offset) => {
4034
+ const instance = instanceRef.current;
4035
+ if (!instance) return false;
4036
+ try {
4037
+ const scrollTo = typeof instance.scrollTo === "function" ? instance.scrollTo.bind(instance) : typeof instance.getScrollResponder === "function" ? instance.getScrollResponder()?.scrollTo?.bind(instance.getScrollResponder()) : void 0;
4038
+ if (typeof scrollTo !== "function") return false;
4039
+ scrollTo(horizontal ? { x: offset, animated: true } : { y: offset, animated: true });
4040
+ metricsRef.current = { ...metricsRef.current, offset };
4041
+ return true;
4042
+ } catch (err) {
4043
+ appilotsDebugWarn(`TrackedScrollView scrollTo failed for "${scrollId}":`, err);
4044
+ return false;
4045
+ }
4046
+ },
4047
+ getScrollMetrics: () => {
4048
+ const m = metricsRef.current;
4049
+ return m.contentLength > 0 ? { ...m } : void 0;
4050
+ }
4051
+ });
4052
+ return () => {
4053
+ listRegistry.unregister(scrollId);
4054
+ };
4055
+ }, [scrollId, label, horizontal]);
4056
+ const onScroll = React2__default.default.useMemo(
4057
+ () => chain(props.onScroll, (event) => {
4058
+ const n = event?.nativeEvent;
4059
+ if (!n) return;
4060
+ const offset = horizontal ? n.contentOffset?.x : n.contentOffset?.y;
4061
+ const visible = horizontal ? n.layoutMeasurement?.width : n.layoutMeasurement?.height;
4062
+ const content = horizontal ? n.contentSize?.width : n.contentSize?.height;
4063
+ metricsRef.current = {
4064
+ offset: typeof offset === "number" ? offset : metricsRef.current.offset,
4065
+ visibleLength: typeof visible === "number" ? visible : metricsRef.current.visibleLength,
4066
+ contentLength: typeof content === "number" ? content : metricsRef.current.contentLength
4067
+ };
4068
+ }),
4069
+ [props.onScroll, horizontal]
4070
+ );
4071
+ const onLayout = React2__default.default.useMemo(
4072
+ () => chain(props.onLayout, (event) => {
4073
+ const size = horizontal ? event?.nativeEvent?.layout?.width : event?.nativeEvent?.layout?.height;
4074
+ if (typeof size === "number") {
4075
+ metricsRef.current = { ...metricsRef.current, visibleLength: size };
4076
+ }
4077
+ }),
4078
+ [props.onLayout, horizontal]
4079
+ );
4080
+ const onContentSizeChange = React2__default.default.useMemo(
4081
+ () => chain(props.onContentSizeChange, (w, h) => {
4082
+ const size = horizontal ? w : h;
4083
+ if (typeof size === "number") {
4084
+ metricsRef.current = { ...metricsRef.current, contentLength: size };
4085
+ }
4086
+ }),
4087
+ [props.onContentSizeChange, horizontal]
4088
+ );
4089
+ return _originalCreateElement(OriginalComponent, {
4090
+ ...props,
4091
+ ref: mergedRef,
4092
+ // Without a throttle iOS fires `onScroll` once per gesture, which
4093
+ // is enough to stay roughly in sync but leaves the offset stale
4094
+ // mid-fling. 16ms is RN's own recommendation for live metrics.
4095
+ scrollEventThrottle: props.scrollEventThrottle ?? 16,
4096
+ onScroll,
4097
+ onLayout,
4098
+ onContentSizeChange,
4099
+ __appilotsScrollId: scrollId
4100
+ });
4101
+ });
4102
+ _scrollWrapperCache.set(OriginalComponent, cached);
4103
+ return cached;
4104
+ }
4105
+ var _enabled2 = false;
3668
4106
  function enableAppilotsAutoTracking(options) {
3669
- if (_enabled) return;
3670
- _enabled = true;
4107
+ if (_enabled2) return;
4108
+ _enabled2 = true;
3671
4109
  const { trackPressable = true, debug = false } = options ?? {};
3672
4110
  if (debug) {
3673
4111
  componentRegistry.subscribe((id, entry) => {
3674
4112
  if (entry) {
3675
- console.log(`[Appilots] Registered ${entry.kind}: "${id}" (${entry.label ?? "no label"})`);
4113
+ appilotsDebugLog(`Registered ${entry.kind}: "${id}" (${entry.label ?? "no label"})`);
3676
4114
  } else {
3677
- console.log(`[Appilots] Unregistered: "${id}"`);
4115
+ appilotsDebugLog(`Unregistered: "${id}"`);
3678
4116
  }
3679
4117
  });
3680
4118
  listRegistry.subscribe((id, entry) => {
3681
4119
  if (entry) {
3682
- console.log(
3683
- `[Appilots] Registered list: "${id}" (${entry.component}, items=${entry.itemCount ?? "unknown"})`
4120
+ appilotsDebugLog(
4121
+ `Registered list: "${id}" (${entry.component}, items=${entry.itemCount ?? "unknown"})`
3684
4122
  );
3685
4123
  } else {
3686
- console.log(`[Appilots] Unregistered list: "${id}"`);
4124
+ appilotsDebugLog(`Unregistered list: "${id}"`);
3687
4125
  }
3688
4126
  });
3689
4127
  }
@@ -3691,12 +4129,14 @@ function enableAppilotsAutoTracking(options) {
3691
4129
  _originalCreateElement = originalCreateElement;
3692
4130
  function isComponent(type, target, name) {
3693
4131
  if (type === target) {
3694
- console.log(`[Appilots] isComponent: matched "${name}" by reference equality`);
4132
+ appilotsDebugLog(`isComponent: matched "${name}" by reference equality`);
3695
4133
  return true;
3696
4134
  }
3697
4135
  const dn = type?.displayName || type?.name || "";
3698
4136
  if (dn === name) {
3699
- console.log(`[Appilots] isComponent: matched "${name}" by displayName/name (reference equality failed \u2014 possible monorepo issue)`);
4137
+ appilotsDebugLog(
4138
+ `isComponent: matched "${name}" by displayName/name (reference equality failed \u2014 possible monorepo issue)`
4139
+ );
3700
4140
  return true;
3701
4141
  }
3702
4142
  return false;
@@ -3705,24 +4145,36 @@ function enableAppilotsAutoTracking(options) {
3705
4145
  if (!props || !type || props.__appilotsInternal) return null;
3706
4146
  const displayName = componentDisplayName(type);
3707
4147
  if (LIST_COMPONENT_NAMES.has(displayName)) {
3708
- console.log(`[Appilots] intercepted list component "${displayName}"`);
4148
+ appilotsDebugLog(`intercepted list component "${displayName}"`);
3709
4149
  return { type: getTrackedList(type, displayName), props };
3710
4150
  }
4151
+ if (SCROLLABLE_COMPONENT_NAMES.has(displayName)) {
4152
+ appilotsDebugLog(`intercepted scrollable "${displayName}"`);
4153
+ return { type: getTrackedScrollView(type, displayName), props };
4154
+ }
3711
4155
  if (isComponent(type, reactNative.TextInput, "TextInput")) {
3712
4156
  if (props.testID || props.accessibilityLabel || props.placeholder) {
3713
- console.log(`[Appilots] intercepted TextInput (testID="${props.testID}", label="${props.accessibilityLabel}", placeholder="${props.placeholder}")`);
4157
+ appilotsDebugLog(
4158
+ // Ids, not the user-facing copy: a placeholder is the app's
4159
+ // own text and often the most identifying string on screen.
4160
+ `intercepted TextInput (testID="${props.testID ?? "?"}" hasLabel=${!!props.accessibilityLabel} hasPlaceholder=${!!props.placeholder})`
4161
+ );
3714
4162
  return { type: TrackedTextInput, props };
3715
4163
  }
3716
4164
  }
3717
4165
  if (isComponent(type, reactNative.Switch, "Switch")) {
3718
4166
  if (props.testID || props.accessibilityLabel) {
3719
- console.log(`[Appilots] intercepted Switch (testID="${props.testID}", label="${props.accessibilityLabel}")`);
4167
+ appilotsDebugLog(
4168
+ `intercepted Switch (testID="${props.testID}", label="${props.accessibilityLabel}")`
4169
+ );
3720
4170
  return { type: TrackedSwitch, props };
3721
4171
  }
3722
4172
  }
3723
4173
  if (isComponent(type, reactNative.TouchableOpacity, "TouchableOpacity") || trackPressable && isComponent(type, reactNative.Pressable, "Pressable")) {
3724
4174
  if ((props.testID || props.accessibilityLabel) && props.onPress) {
3725
- console.log(`[Appilots] intercepted Pressable/TouchableOpacity (testID="${props.testID}", label="${props.accessibilityLabel}")`);
4175
+ appilotsDebugLog(
4176
+ `intercepted Pressable/TouchableOpacity (testID="${props.testID}", label="${props.accessibilityLabel}")`
4177
+ );
3726
4178
  return { type: getTrackedPressable(type), props };
3727
4179
  }
3728
4180
  }
@@ -3736,14 +4188,17 @@ function enableAppilotsAutoTracking(options) {
3736
4188
  }
3737
4189
  return originalCreateElement(type, props, ...children);
3738
4190
  };
3739
- console.log("[Appilots] Auto-tracking: React.createElement patched");
4191
+ _createElementPatched = true;
4192
+ appilotsDebugLog("Auto-tracking: React.createElement patched");
3740
4193
  try {
3741
4194
  const jsxRuntime = __require("react/jsx-runtime");
3742
4195
  if (jsxRuntime) {
3743
4196
  _patchJsxRuntimeModule(jsxRuntime);
3744
4197
  }
3745
4198
  } catch (e) {
3746
- console.log("[Appilots] Auto-tracking: react/jsx-runtime not available internally (will be patched by metro shim)");
4199
+ appilotsDebugLog(
4200
+ "Auto-tracking: react/jsx-runtime not available internally (will be patched by metro shim)"
4201
+ );
3747
4202
  }
3748
4203
  try {
3749
4204
  const jsxDevRuntime = __require("react/jsx-dev-runtime");
@@ -3751,11 +4206,14 @@ function enableAppilotsAutoTracking(options) {
3751
4206
  _patchJsxDevRuntimeModule(jsxDevRuntime);
3752
4207
  }
3753
4208
  } catch (e) {
3754
- console.log("[Appilots] Auto-tracking: react/jsx-dev-runtime not available internally (will be patched by metro shim)");
4209
+ appilotsDebugLog(
4210
+ "Auto-tracking: react/jsx-dev-runtime not available internally (will be patched by metro shim)"
4211
+ );
3755
4212
  }
3756
4213
  }
3757
4214
  var _jsxRuntimePatched = false;
3758
4215
  var _jsxDevRuntimePatched = false;
4216
+ var _createElementPatched = false;
3759
4217
  function _patchJsxRuntimeModule(jsxRuntime) {
3760
4218
  if (_jsxRuntimePatched || !isInterceptionActive() || !jsxRuntime) return;
3761
4219
  _jsxRuntimePatched = true;
@@ -3769,7 +4227,7 @@ function _patchJsxRuntimeModule(jsxRuntime) {
3769
4227
  }
3770
4228
  return origJsx(type, props, key);
3771
4229
  };
3772
- console.log("[Appilots] Auto-tracking: react/jsx-runtime jsx() patched");
4230
+ appilotsDebugLog("Auto-tracking: react/jsx-runtime jsx() patched");
3773
4231
  }
3774
4232
  if (origJsxs) {
3775
4233
  jsxRuntime.jsxs = function(type, props, key) {
@@ -3779,7 +4237,7 @@ function _patchJsxRuntimeModule(jsxRuntime) {
3779
4237
  }
3780
4238
  return origJsxs(type, props, key);
3781
4239
  };
3782
- console.log("[Appilots] Auto-tracking: react/jsx-runtime jsxs() patched");
4240
+ appilotsDebugLog("Auto-tracking: react/jsx-runtime jsxs() patched");
3783
4241
  }
3784
4242
  }
3785
4243
  function _patchJsxDevRuntimeModule(jsxDevRuntime) {
@@ -3794,58 +4252,75 @@ function _patchJsxDevRuntimeModule(jsxDevRuntime) {
3794
4252
  }
3795
4253
  return origJsxDEV(type, props, key, isStaticChildren, source, self);
3796
4254
  };
3797
- console.log("[Appilots] Auto-tracking: react/jsx-dev-runtime jsxDEV() patched");
4255
+ appilotsDebugLog("Auto-tracking: react/jsx-dev-runtime jsxDEV() patched");
3798
4256
  }
3799
4257
  }
3800
4258
  function _patchJsxRuntimes(jsxRuntime, jsxDevRuntime) {
3801
4259
  if (!isInterceptionActive()) {
3802
- console.warn("[Appilots] _patchJsxRuntimes called but auto-tracking is not enabled");
4260
+ appilotsDebugWarn("_patchJsxRuntimes called but auto-tracking is not enabled");
3803
4261
  return;
3804
4262
  }
3805
- console.log("[Appilots] _patchJsxRuntimes: patching from app context (metro shim)");
4263
+ appilotsDebugLog("_patchJsxRuntimes: patching from app context (metro shim)");
3806
4264
  if (jsxRuntime) _patchJsxRuntimeModule(jsxRuntime);
3807
4265
  if (jsxDevRuntime) _patchJsxDevRuntimeModule(jsxDevRuntime);
3808
4266
  }
3809
4267
  function isAutoTrackingEnabled() {
3810
- return _enabled;
4268
+ return _enabled2;
4269
+ }
4270
+ function getAutoTrackingState() {
4271
+ return {
4272
+ enabled: _enabled2,
4273
+ createElementPatched: _createElementPatched,
4274
+ jsxRuntimePatched: _jsxRuntimePatched,
4275
+ jsxDevRuntimePatched: _jsxDevRuntimePatched,
4276
+ interceptionActive: isInterceptionActive()
4277
+ };
3811
4278
  }
3812
4279
 
3813
4280
  // src/core/initAppilots.ts
3814
4281
  var _globalConfig = null;
3815
4282
  var _autoConfigAttempted = false;
3816
4283
  function initAppilots(config) {
3817
- console.log(`[Appilots] initAppilots called with config: projectId="${config.projectId}", apiBaseUrl="${config.apiBaseUrl ?? config.apiUrl ?? "default"}", debug=${config.debug ?? false}, autoTracking=${JSON.stringify(config.autoTracking ?? {})}`);
3818
- if (config.apiUrl && !config.apiBaseUrl) {
3819
- config.apiBaseUrl = config.apiUrl;
4284
+ appilotsDebugLog(
4285
+ `initAppilots called with config: projectId="${config.projectId}", apiBaseUrl="${config.apiBaseUrl ?? config.apiUrl ?? "default"}", debug=${config.debug ?? false}, autoTracking=${JSON.stringify(config.autoTracking ?? {})}`
4286
+ );
4287
+ if (!config.apiBaseUrl) {
4288
+ config.apiBaseUrl = config.apiUrl ?? config.serverUrl;
3820
4289
  }
3821
4290
  _globalConfig = config;
3822
4291
  const autoTrackingEnabled = config.autoTracking?.enabled !== false;
3823
4292
  if (autoTrackingEnabled) {
3824
- console.log("[Appilots] initAppilots: auto-tracking is enabled, calling enableAppilotsAutoTracking()");
4293
+ appilotsDebugLog(
4294
+ "initAppilots: auto-tracking is enabled, calling enableAppilotsAutoTracking()"
4295
+ );
3825
4296
  enableAppilotsAutoTracking({
3826
4297
  trackPressable: config.autoTracking?.trackPressable ?? true,
3827
4298
  debug: config.debug ?? false
3828
4299
  });
3829
4300
  } else {
3830
- console.log("[Appilots] initAppilots: auto-tracking is explicitly disabled");
4301
+ appilotsDebugLog("initAppilots: auto-tracking is explicitly disabled");
3831
4302
  }
3832
4303
  }
3833
4304
  function tryAutoConfig() {
3834
4305
  if (_globalConfig || _autoConfigAttempted) {
3835
- console.log(`[Appilots] tryAutoConfig: skipping (alreadyConfigured=${!!_globalConfig}, alreadyAttempted=${_autoConfigAttempted})`);
4306
+ appilotsDebugLog(
4307
+ `tryAutoConfig: skipping (alreadyConfigured=${!!_globalConfig}, alreadyAttempted=${_autoConfigAttempted})`
4308
+ );
3836
4309
  return;
3837
4310
  }
3838
4311
  _autoConfigAttempted = true;
3839
4312
  try {
3840
4313
  const rc = globalThis.__APPILOTS_RC__;
3841
4314
  if (rc && typeof rc === "object" && rc.projectId) {
3842
- console.log(`[Appilots] tryAutoConfig: found globalThis.__APPILOTS_RC__ with projectId="${rc.projectId}"`);
4315
+ appilotsDebugLog(
4316
+ `tryAutoConfig: found globalThis.__APPILOTS_RC__ with projectId="${rc.projectId}"`
4317
+ );
3843
4318
  initAppilots(rc);
3844
4319
  } else {
3845
- console.log("[Appilots] tryAutoConfig: globalThis.__APPILOTS_RC__ not found or missing projectId");
4320
+ appilotsDebugLog("tryAutoConfig: globalThis.__APPILOTS_RC__ not found or missing projectId");
3846
4321
  }
3847
4322
  } catch {
3848
- console.warn("[Appilots] tryAutoConfig: error reading globalThis.__APPILOTS_RC__");
4323
+ appilotsDebugWarn("tryAutoConfig: error reading globalThis.__APPILOTS_RC__");
3849
4324
  }
3850
4325
  }
3851
4326
  function getGlobalConfig() {
@@ -3862,7 +4337,7 @@ var _diagnostics = {
3862
4337
  };
3863
4338
  function setFiberRoot(fiber) {
3864
4339
  if (_fiberRoot !== fiber) {
3865
- console.log(`[Appilots] Introspection: fiber root captured (type=${typeName(fiber)})`);
4340
+ appilotsDebugLog(`Introspection: fiber root captured (type=${typeName(fiber)})`);
3866
4341
  _fiberRoot = fiber;
3867
4342
  }
3868
4343
  _diagnostics.captured = true;
@@ -3899,262 +4374,976 @@ function typeName(fiber) {
3899
4374
  if (!t) return "unknown";
3900
4375
  if (typeof t === "string") return t;
3901
4376
  if (typeof t === "function") return t.displayName ?? t.name ?? "anonymous";
3902
- if (typeof t === "object") return t.displayName ?? t.render?.displayName ?? t.render?.name ?? "forwardRef/memo";
4377
+ if (typeof t === "object")
4378
+ return t.displayName ?? t.render?.displayName ?? t.render?.name ?? "forwardRef/memo";
3903
4379
  return String(t);
3904
4380
  }
3905
4381
 
3906
- // src/version.ts
3907
- var SDK_VERSION2 = "0.8.0";
3908
- var FiberSentinel = class extends React2__default.default.Component {
3909
- componentDidMount() {
3910
- const fiber = this._reactInternals ?? this._reactInternalFiber ?? null;
3911
- if (!fiber) {
3912
- const reactVersion = React2__default.default.version ?? null;
3913
- console.warn(
3914
- `[Appilots] FiberSentinel: this._reactInternals is undefined; snapshot capture will fall back to render-time owner. React version may be incompatible (react=${reactVersion}).`
3915
- );
3916
- recordIntrospectionFailure("sentinel-missing-internals", reactVersion);
3917
- this.props.onUnavailable?.({ reactVersion });
3918
- return;
4382
+ // src/introspection/textPolicy.ts
4383
+ function isReferenceable(text) {
4384
+ return typeof text === "string" && /[\p{L}\p{N}]/u.test(text);
4385
+ }
4386
+ function foldLatinDiacritics(text) {
4387
+ return text.normalize("NFD").replace(/[\u0300-\u036f]/g, "").normalize("NFC");
4388
+ }
4389
+ function foldForCompare(text) {
4390
+ if (!text) return "";
4391
+ return foldLatinDiacritics(text.toLowerCase()).replace(/[^\p{L}\p{N}]/gu, "");
4392
+ }
4393
+ function labelToId(label) {
4394
+ if (!label) return "";
4395
+ return foldLatinDiacritics(label.toLowerCase()).replace(/[^\p{L}\p{N}\s]/gu, "").trim().replace(/\s+(.)/gu, (_, c) => c.toUpperCase()).replace(/\s+/gu, "");
4396
+ }
4397
+
4398
+ // src/introspection/geometry.ts
4399
+ var _host = null;
4400
+ var _asked = 0;
4401
+ var _answered = 0;
4402
+ function setGeometryHost(host) {
4403
+ _host = host;
4404
+ _asked = 0;
4405
+ _answered = 0;
4406
+ }
4407
+ function geometryAvailability() {
4408
+ if (!_host) return "unavailable";
4409
+ if (_asked > 0 && _answered === 0) return "silent";
4410
+ return "available";
4411
+ }
4412
+ function geometryStats() {
4413
+ return { asked: _asked, answered: _answered };
4414
+ }
4415
+ function isUsableRect(rect) {
4416
+ return !!rect && Number.isFinite(rect.x) && Number.isFinite(rect.y) && Number.isFinite(rect.width) && Number.isFinite(rect.height);
4417
+ }
4418
+ var HOST_SEARCH_DEPTH = 6;
4419
+ function rectOfFiber(fiber) {
4420
+ const host = _host;
4421
+ if (!host || !fiber) return null;
4422
+ const stack = [{ node: fiber, depth: 0 }];
4423
+ let consulted = false;
4424
+ while (stack.length > 0) {
4425
+ const { node, depth } = stack.pop();
4426
+ const current = node;
4427
+ if (!current) continue;
4428
+ if (current.stateNode) {
4429
+ consulted = true;
4430
+ let rect = null;
4431
+ try {
4432
+ rect = host.rectOf(current.stateNode);
4433
+ } catch {
4434
+ rect = null;
4435
+ }
4436
+ if (isUsableRect(rect)) {
4437
+ _asked += 1;
4438
+ _answered += 1;
4439
+ return rect;
4440
+ }
4441
+ }
4442
+ if (depth < HOST_SEARCH_DEPTH && current.child) {
4443
+ stack.push({ node: current.child, depth: depth + 1 });
4444
+ const sibling = current.child.sibling;
4445
+ if (sibling) stack.push({ node: sibling, depth: depth + 1 });
3919
4446
  }
3920
- let top = fiber;
3921
- while (top.return) top = top.return;
3922
- setFiberRoot(top);
3923
- }
3924
- render() {
3925
- return this.props.children ?? null;
3926
- }
3927
- };
3928
- function AppilotsFiberRoot({
3929
- children,
3930
- onUnavailable
3931
- }) {
3932
- const Internals = React2__default.default.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED_IN_THIS_VERSION_OF_REACT;
3933
- const ownerFiber = Internals?.ReactCurrentOwner?.current;
3934
- if (ownerFiber && !getFiberRoot()) {
3935
- setFiberRoot(ownerFiber);
3936
4447
  }
3937
- return /* @__PURE__ */ React2__default.default.createElement(FiberSentinel, { onUnavailable }, children);
4448
+ if (consulted) _asked += 1;
4449
+ return null;
3938
4450
  }
3939
-
3940
- // src/debug/logger.ts
3941
- var debugOverride;
3942
- function setAppilotsDebugEnabled(enabled) {
3943
- debugOverride = enabled;
4451
+ function isRectOnScreen(rect, window) {
4452
+ if (rect.width <= 0 || rect.height <= 0) return false;
4453
+ if (rect.x >= window.width || rect.y >= window.height) return false;
4454
+ if (rect.x + rect.width <= 0 || rect.y + rect.height <= 0) return false;
4455
+ return true;
3944
4456
  }
3945
- function isDebugEnabled() {
3946
- if (debugOverride !== void 0) return debugOverride;
3947
- return getGlobalConfig()?.debug === true;
4457
+ var _rectByNode = /* @__PURE__ */ new WeakMap();
4458
+ function readGeometry(fiber) {
4459
+ const host = _host;
4460
+ if (!host) return { rect: null, fields: {} };
4461
+ const rect = rectOfFiber(fiber);
4462
+ if (!rect) return { rect: null, fields: {} };
4463
+ return { rect, fields: { onScreen: isRectOnScreen(rect, host.window) } };
3948
4464
  }
3949
- function appilotsDebugLog(...args) {
3950
- if (!isDebugEnabled()) return;
3951
- console.log("[Appilots]", ...args);
4465
+ function withRect(node, reading) {
4466
+ if (reading.rect) _rectByNode.set(node, reading.rect);
4467
+ return node;
3952
4468
  }
3953
- function appilotsDebugWarn(...args) {
3954
- if (!isDebugEnabled()) return;
3955
- console.log("[Appilots]", ...args);
4469
+ function rectForNode(node) {
4470
+ if (!node || typeof node !== "object") return void 0;
4471
+ return _rectByNode.get(node);
3956
4472
  }
3957
- var AppilotsErrorBoundary = class extends React2__default.default.Component {
3958
- state = { degraded: false, escalate: null };
3959
- /**
3960
- * Instance field rather than state: `componentDidCatch` needs to know
3961
- * whether THIS is the first failure, and by the time it runs
3962
- * `getDerivedStateFromError` has already flipped `state.degraded`.
3963
- */
3964
- hasDegradedOnce = false;
3965
- static getDerivedStateFromError() {
3966
- return { degraded: true };
4473
+
4474
+ // src/introspection/walkFiber.ts
4475
+ function getTypeName(type) {
4476
+ if (!type) return null;
4477
+ if (typeof type === "string") return type;
4478
+ if (typeof type === "function") {
4479
+ return type.displayName ?? type.name ?? null;
3967
4480
  }
3968
- componentDidCatch(error, info) {
3969
- if (this.hasDegradedOnce) {
3970
- this.setState({ escalate: error ?? new Error("Appilots: re-thrown host render error") });
3971
- return;
3972
- }
3973
- this.hasDegradedOnce = true;
3974
- console.error(
3975
- `[Appilots] The Appilots ${this.props.surface} threw while rendering and has been disabled for the rest of this session. Your app keeps running \u2014 this is an Appilots bug, not a bug in your code. Please report it with the stack below: https://github.com/Axtern-Labs/Appilots/issues`,
3976
- error
3977
- );
3978
- try {
3979
- this.props.onError?.(error, info?.componentStack ?? null);
3980
- } catch {
4481
+ if (typeof type === "object") {
4482
+ if (type.displayName) return type.displayName;
4483
+ if (type.render) {
4484
+ return type.render.displayName ?? type.render.name ?? null;
3981
4485
  }
4486
+ if (type.type) return getTypeName(type.type);
3982
4487
  }
3983
- render() {
3984
- if (this.state.escalate !== null) throw this.state.escalate;
3985
- if (this.state.degraded) return this.props.fallback;
3986
- return this.props.children;
4488
+ return null;
4489
+ }
4490
+ function matchesName(fiber, name) {
4491
+ return getTypeName(fiber.type) === name || getTypeName(fiber.elementType) === name;
4492
+ }
4493
+ function extractTextFromChildren(children) {
4494
+ if (children == null) return "";
4495
+ if (typeof children === "string") return children;
4496
+ if (typeof children === "number") return String(children);
4497
+ if (Array.isArray(children)) {
4498
+ return children.map((c) => extractTextFromChildren(c)).filter(Boolean).join("");
3987
4499
  }
3988
- };
3989
-
3990
- // src/context/AppilotsProvider.tsx
3991
- var AppilotsContext = React2.createContext(null);
3992
- function createInertClient() {
3993
- const unavailable = () => Promise.reject(
3994
- new Error("Appilots is unavailable: the SDK degraded after a render error.")
3995
- );
3996
- return new Proxy({}, {
3997
- get: (_target, prop) => {
3998
- if (prop === "then" || typeof prop === "symbol") return void 0;
3999
- return unavailable;
4500
+ return "";
4501
+ }
4502
+ function findChildText(fiber) {
4503
+ const stack = [];
4504
+ if (fiber.child) stack.push(fiber.child);
4505
+ const visited = /* @__PURE__ */ new WeakSet();
4506
+ const texts = [];
4507
+ const glyphs = [];
4508
+ while (stack.length > 0) {
4509
+ const node = stack.pop();
4510
+ if (!node || visited.has(node)) continue;
4511
+ visited.add(node);
4512
+ if (matchesName(node, "Text")) {
4513
+ const text = extractTextFromChildren(node.memoizedProps?.children);
4514
+ const trimmed = text.trim();
4515
+ if (isReferenceable(trimmed)) {
4516
+ texts.push(trimmed);
4517
+ if (texts.length >= 3) break;
4518
+ } else if (trimmed && glyphs.length < 3) {
4519
+ glyphs.push(trimmed);
4520
+ }
4521
+ }
4522
+ if (node.sibling) stack.push(node.sibling);
4523
+ if (node.child) stack.push(node.child);
4524
+ }
4525
+ if (texts.length > 0) return texts.join(" \xB7 ");
4526
+ return glyphs.length > 0 ? glyphs.join(" \xB7 ") : void 0;
4527
+ }
4528
+ var SELF_REFERENTIAL_COMPONENTS = /* @__PURE__ */ new Set([
4529
+ "AppilotsChat",
4530
+ "AppilotsChatInner",
4531
+ "ActionBreadcrumb",
4532
+ "ConfirmDialog"
4533
+ ]);
4534
+ function isAppilotsSkipMarked(props) {
4535
+ if (!props) return false;
4536
+ return props.__appilotsSkip === true || props["data-appilots-skip"] === true || props.appilotsSkip === true;
4537
+ }
4538
+ function isAppilotsSensitiveMarked(props) {
4539
+ if (!props) return false;
4540
+ return props.__appilotsSensitive === true || props["data-appilots-sensitive"] === true || props.appilotsSensitive === true;
4541
+ }
4542
+ function stringProp(props, name) {
4543
+ const value = props?.[name];
4544
+ return typeof value === "string" && value.length > 0 ? value : void 0;
4545
+ }
4546
+ function numberProp(props, name) {
4547
+ const value = props?.[name];
4548
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
4549
+ }
4550
+ function shouldSkipSubtree(fiber) {
4551
+ const props = fiber.memoizedProps;
4552
+ if (!props) return false;
4553
+ const typeName2 = getTypeName(fiber.type) ?? getTypeName(fiber.elementType);
4554
+ if (typeName2 && SELF_REFERENTIAL_COMPONENTS.has(typeName2)) return true;
4555
+ if (isAppilotsSkipMarked(props)) return true;
4556
+ if (isModalFiber(fiber) && props.visible === false) return true;
4557
+ if (matchesName(fiber, "Screen") && props.activityState === 0) return true;
4558
+ const style = props.style;
4559
+ if (style) {
4560
+ const flat = Array.isArray(style) ? Object.assign({}, ...style.filter(Boolean)) : style;
4561
+ if (flat?.display === "none") return true;
4562
+ if (flat?.opacity === 0 && props.pointerEvents === "none") return true;
4563
+ }
4564
+ return false;
4565
+ }
4566
+ function inferInputType(props) {
4567
+ if (props.secureTextEntry || isAppilotsSensitiveMarked(props)) return "password";
4568
+ switch (props.keyboardType) {
4569
+ case "email-address":
4570
+ return "email";
4571
+ case "numeric":
4572
+ case "number-pad":
4573
+ case "decimal-pad":
4574
+ return "number";
4575
+ case "phone-pad":
4576
+ return "phone";
4577
+ default:
4578
+ return "text";
4579
+ }
4580
+ }
4581
+ function inferLabel(props) {
4582
+ return props.accessibilityLabel ?? props.label ?? props.placeholder ?? props.testID;
4583
+ }
4584
+ function resolveIdentity(props, label) {
4585
+ const declared = stringProp(props, "testID") ?? stringProp(props, "accessibilityLabel");
4586
+ if (declared) return { id: declared, provenance: "declared" };
4587
+ const derived = labelToId(label);
4588
+ if (derived) return { id: derived, provenance: "derived" };
4589
+ return { provenance: "positional" };
4590
+ }
4591
+ function isSwitchLike(fiber, props) {
4592
+ if (props?.accessibilityRole === "switch" || props?.role === "switch") return true;
4593
+ return matchesName(fiber, "Switch");
4594
+ }
4595
+ function switchValue(props) {
4596
+ if (typeof props?.value === "boolean") return props.value;
4597
+ const checked = props?.accessibilityState?.checked;
4598
+ return checked === true;
4599
+ }
4600
+ function nearbyLabelForSwitch(fiber) {
4601
+ let parent = fiber?.return;
4602
+ for (let depth = 0; parent && depth < 3; depth++, parent = parent.return) {
4603
+ const text = findChildText(parent);
4604
+ if (text && isReferenceable(text)) return text;
4605
+ }
4606
+ return void 0;
4607
+ }
4608
+ var ALWAYS_LIST_NAMES = /* @__PURE__ */ new Set([
4609
+ "FlatList",
4610
+ "SectionList",
4611
+ "VirtualizedList",
4612
+ // Shopify's high-perf FlatList replacement — same usage shape, same
4613
+ // virtualization, same per-item key.
4614
+ "FlashList"
4615
+ ]);
4616
+ var HEURISTIC_LIST_NAMES = /* @__PURE__ */ new Set(["ScrollView"]);
4617
+ var LIST_CELL_NAMES = /* @__PURE__ */ new Set([
4618
+ "CellRenderer",
4619
+ "CellRendererComponent",
4620
+ "VirtualizedListCellContextProvider"
4621
+ ]);
4622
+ function looksLikeListContainerByProps(props) {
4623
+ if (!props || typeof props !== "object") return false;
4624
+ if (typeof props.renderItem !== "function") return false;
4625
+ if (Array.isArray(props.sections)) return true;
4626
+ if (Array.isArray(props.data)) return true;
4627
+ return typeof props.getItemCount === "function" && typeof props.getItem === "function";
4628
+ }
4629
+ function isAlwaysListContainer(fiber) {
4630
+ for (const name of ALWAYS_LIST_NAMES) {
4631
+ if (matchesName(fiber, name)) return true;
4632
+ }
4633
+ return looksLikeListContainerByProps(fiber?.memoizedProps);
4634
+ }
4635
+ function isHeuristicListContainer(fiber) {
4636
+ for (const name of HEURISTIC_LIST_NAMES) {
4637
+ if (matchesName(fiber, name)) return true;
4638
+ }
4639
+ return false;
4640
+ }
4641
+ function isKnownListCell(fiber) {
4642
+ const typeName2 = getTypeName(fiber.type) ?? getTypeName(fiber.elementType);
4643
+ return !!typeName2 && LIST_CELL_NAMES.has(typeName2);
4644
+ }
4645
+ var MODAL_HOST_NAMES = /* @__PURE__ */ new Set(["RCTModalHostView", "ModalHostView"]);
4646
+ function isModalHostFiber(fiber) {
4647
+ const type = fiber?.type;
4648
+ return typeof type === "string" && MODAL_HOST_NAMES.has(type);
4649
+ }
4650
+ function isModalFiber(fiber) {
4651
+ return matchesName(fiber, "Modal") || isModalHostFiber(fiber);
4652
+ }
4653
+ function isVisibleModalFiber(fiber) {
4654
+ return isModalFiber(fiber) && fiber?.memoizedProps?.visible !== false;
4655
+ }
4656
+ function collectVisibleModalFibers(container, excludeRoots = []) {
4657
+ const excluded = /* @__PURE__ */ new WeakSet();
4658
+ for (const rootFiber of excludeRoots) {
4659
+ if (rootFiber) excluded.add(rootFiber);
4660
+ }
4661
+ const found = [];
4662
+ const visited = /* @__PURE__ */ new WeakSet();
4663
+ const stack = [];
4664
+ if (container?.child) stack.push(container.child);
4665
+ while (stack.length > 0) {
4666
+ const node = stack.pop();
4667
+ if (!node || visited.has(node)) continue;
4668
+ visited.add(node);
4669
+ if (node.sibling) stack.push(node.sibling);
4670
+ if (excluded.has(node) || shouldSkipSubtree(node)) continue;
4671
+ if (isVisibleModalFiber(node)) {
4672
+ found.push(node);
4673
+ continue;
4674
+ }
4675
+ if (node.child) stack.push(node.child);
4676
+ }
4677
+ return found;
4678
+ }
4679
+ function isMarkedListItem(fiber, listId) {
4680
+ const props = fiber?.memoizedProps;
4681
+ if (!props || props.__appilotsListItem !== true) return false;
4682
+ if (!listId) return true;
4683
+ return props.__appilotsListId === listId;
4684
+ }
4685
+ function listIdFromProps(props) {
4686
+ return stringProp(props, "__appilotsListId") ?? stringProp(props, "appilotsListId") ?? // A ScrollView that turned out to hold keyed rows is BOTH: the
4687
+ // walker sees a list, auto-tracking registered a scrollable. Sharing
4688
+ // the id lets the two merge instead of describing one surface twice.
4689
+ stringProp(props, "__appilotsScrollId");
4690
+ }
4691
+ function explicitListIdFromProps(props) {
4692
+ return stringProp(props, "__appilotsListId") ?? stringProp(props, "appilotsListId");
4693
+ }
4694
+ function listMetadataFromProps(props) {
4695
+ const id = listIdFromProps(props);
4696
+ const itemCount = numberProp(props, "__appilotsItemCount");
4697
+ const label = stringProp(props, "__appilotsListLabel") ?? stringProp(props, "accessibilityLabel") ?? stringProp(props, "testID");
4698
+ const refreshing = props?.refreshing === true || props?.__appilotsRefreshing === true;
4699
+ const empty = props?.__appilotsEmpty === true || typeof itemCount === "number" && itemCount === 0;
4700
+ return {
4701
+ id,
4702
+ itemCount,
4703
+ refreshing,
4704
+ empty,
4705
+ label,
4706
+ source: id ? "auto-tracked" : void 0
4707
+ };
4708
+ }
4709
+ function hasRenderableContent(fiber) {
4710
+ const stack = [fiber];
4711
+ const visited = /* @__PURE__ */ new WeakSet();
4712
+ while (stack.length > 0) {
4713
+ const node = stack.pop();
4714
+ if (!node || visited.has(node)) continue;
4715
+ visited.add(node);
4716
+ if (shouldSkipSubtree(node) || isModalFiber(node)) continue;
4717
+ const props = node.memoizedProps ?? {};
4718
+ if (matchesName(node, "Text")) {
4719
+ const text = extractTextFromChildren(props.children);
4720
+ if (text && text.trim().length > 0) return true;
4721
+ }
4722
+ if (matchesName(node, "TextInput") || matchesName(node, "Switch")) {
4723
+ return true;
4000
4724
  }
4725
+ if (TOUCHABLE_NAMES.has(getTypeName(node.type) ?? "") || TOUCHABLE_NAMES.has(getTypeName(node.elementType) ?? "")) {
4726
+ return true;
4727
+ }
4728
+ if (node.sibling) stack.push(node.sibling);
4729
+ if (node.child) stack.push(node.child);
4730
+ }
4731
+ return false;
4732
+ }
4733
+ function findListCellsByType(container, minItems) {
4734
+ const cells = [];
4735
+ const visited = /* @__PURE__ */ new WeakSet();
4736
+ const stack = [];
4737
+ if (container?.child) stack.push(container.child);
4738
+ while (stack.length > 0) {
4739
+ const node = stack.pop();
4740
+ if (!node || visited.has(node)) continue;
4741
+ visited.add(node);
4742
+ if (node.sibling) stack.push(node.sibling);
4743
+ if (shouldSkipSubtree(node) || isModalFiber(node)) continue;
4744
+ if (isAlwaysListContainer(node)) continue;
4745
+ if (isKnownListCell(node) && hasRenderableContent(node)) {
4746
+ cells.push(node);
4747
+ continue;
4748
+ }
4749
+ if (node.child) stack.push(node.child);
4750
+ }
4751
+ return cells.length >= minItems ? cells : null;
4752
+ }
4753
+ function findMarkedListItems(container, minItems, listId) {
4754
+ const items = [];
4755
+ const visited = /* @__PURE__ */ new WeakSet();
4756
+ const stack = [];
4757
+ if (container?.child) stack.push(container.child);
4758
+ while (stack.length > 0) {
4759
+ const node = stack.pop();
4760
+ if (!node || visited.has(node)) continue;
4761
+ visited.add(node);
4762
+ if (node.sibling) stack.push(node.sibling);
4763
+ if (shouldSkipSubtree(node) || isModalFiber(node)) continue;
4764
+ if (isMarkedListItem(node, listId)) {
4765
+ items.push(node);
4766
+ continue;
4767
+ }
4768
+ if (node.child) stack.push(node.child);
4769
+ }
4770
+ if (items.length < minItems) return null;
4771
+ items.sort((a, b) => {
4772
+ const ai = numberProp(a.memoizedProps, "__appilotsItemIndex");
4773
+ const bi = numberProp(b.memoizedProps, "__appilotsItemIndex");
4774
+ if (ai === void 0 && bi === void 0) return 0;
4775
+ if (ai === void 0) return 1;
4776
+ if (bi === void 0) return -1;
4777
+ return ai - bi;
4001
4778
  });
4779
+ return items;
4780
+ }
4781
+ function hasAuthoredReactKey(fiber) {
4782
+ const key = fiber?.key;
4783
+ if (key === null || key === void 0 || key === "") return false;
4784
+ const s = String(key);
4785
+ if (s.startsWith(".") && !s.startsWith(".$")) return false;
4786
+ return true;
4787
+ }
4788
+ function findKeyedSiblingItems(container, minItems) {
4789
+ if (!container) return null;
4790
+ let current = container;
4791
+ const visited = /* @__PURE__ */ new WeakSet();
4792
+ while (current && !visited.has(current)) {
4793
+ visited.add(current);
4794
+ const siblings = [];
4795
+ let cur = current.child;
4796
+ while (cur) {
4797
+ if (!isModalFiber(cur)) siblings.push(cur);
4798
+ cur = cur.sibling;
4799
+ }
4800
+ if (siblings.length === 0) return null;
4801
+ const keyed = siblings.filter(hasAuthoredReactKey);
4802
+ if (keyed.length >= minItems) {
4803
+ return keyed;
4804
+ }
4805
+ if (siblings.length > 1) {
4806
+ return null;
4807
+ }
4808
+ current = siblings[0];
4809
+ }
4810
+ return null;
4002
4811
  }
4003
- function createDegradedContext(config) {
4812
+ function findListItems(container, minItems = 2, preferCells = false) {
4813
+ if (!container) return null;
4814
+ const listId = listIdFromProps(container.memoizedProps);
4815
+ const markedItems = findMarkedListItems(container, minItems, listId);
4816
+ if (markedItems) return markedItems;
4817
+ if (preferCells) {
4818
+ const cells = findListCellsByType(container, minItems);
4819
+ if (cells) return cells;
4820
+ }
4821
+ if (minItems <= 1) {
4822
+ const multiItemLevel = findKeyedSiblingItems(container, 2);
4823
+ if (multiItemLevel) return multiItemLevel;
4824
+ }
4825
+ return findKeyedSiblingItems(container, minItems) ?? findListCellsByType(container, minItems);
4826
+ }
4827
+ var TOUCHABLE_NAMES = /* @__PURE__ */ new Set([
4828
+ "TouchableOpacity",
4829
+ "TouchableHighlight",
4830
+ "TouchableWithoutFeedback",
4831
+ "TouchableNativeFeedback",
4832
+ "Pressable"
4833
+ ]);
4834
+ function hasPressHandler(fiber) {
4835
+ const props = fiber?.memoizedProps ?? {};
4836
+ return typeof props.onPress === "function" || typeof props.onPressIn === "function";
4837
+ }
4838
+ function isPressableTarget(fiber) {
4839
+ return TOUCHABLE_NAMES.has(getTypeName(fiber.type) ?? "") || TOUCHABLE_NAMES.has(getTypeName(fiber.elementType) ?? "") || hasPressHandler(fiber);
4840
+ }
4841
+ function emptyDetectorCounts() {
4004
4842
  return {
4005
- config: config ?? { projectId: "" },
4006
- client: createInertClient(),
4007
- subscribe: () => () => {
4008
- },
4009
- emit: () => {
4010
- },
4011
- remotePersonalization: null,
4012
- degraded: true
4843
+ Text: 0,
4844
+ TextInput: 0,
4845
+ Switch: 0,
4846
+ ActivityIndicator: 0,
4847
+ Modal: 0,
4848
+ adjustable: 0,
4849
+ pressable: 0,
4850
+ listContainers: 0
4013
4851
  };
4014
4852
  }
4015
- function AppilotsProvider({ config, children, client }) {
4016
- return /* @__PURE__ */ React2__default.default.createElement(
4017
- AppilotsErrorBoundary,
4018
- {
4019
- surface: "provider",
4020
- onError: reportProviderRenderFailure,
4021
- fallback: /* @__PURE__ */ React2__default.default.createElement(AppilotsContext.Provider, { value: createDegradedContext(config ?? null) }, children)
4022
- },
4023
- /* @__PURE__ */ React2__default.default.createElement(AppilotsProviderInner, { config, client }, children)
4024
- );
4853
+ function totalDetectorHits(counts) {
4854
+ return counts.Text + counts.TextInput + counts.Switch + counts.ActivityIndicator + counts.Modal + counts.adjustable + counts.pressable + counts.listContainers;
4025
4855
  }
4026
- function reportProviderRenderFailure() {
4027
- recordRenderFailure(readReactVersion());
4856
+ function addDetectorCounts(into, from) {
4857
+ into.Text += from.Text;
4858
+ into.TextInput += from.TextInput;
4859
+ into.Switch += from.Switch;
4860
+ into.ActivityIndicator += from.ActivityIndicator;
4861
+ into.Modal += from.Modal;
4862
+ into.adjustable += from.adjustable;
4863
+ into.pressable += from.pressable;
4864
+ into.listContainers += from.listContainers;
4028
4865
  }
4029
- function readReactVersion() {
4030
- const version = React2__default.default.version;
4031
- return typeof version === "string" ? version : null;
4866
+ var _lastWalk = null;
4867
+ function getLastWalkDiagnostics() {
4868
+ return _lastWalk ? { ..._lastWalk, detectors: { ..._lastWalk.detectors } } : null;
4032
4869
  }
4033
- function AppilotsProviderInner({ config: configProp, children, client: externalClient }) {
4034
- console.log(`[Appilots] AppilotsProvider initializing \u2014 config source: ${configProp ? "prop" : "auto (globalThis.__APPILOTS_RC__)"}`);
4035
- tryAutoConfig();
4036
- const globalConfig = getGlobalConfig();
4037
- console.log(`[Appilots] AppilotsProvider: configProp=${!!configProp}, globalConfig=${!!globalConfig}`);
4038
- const config = configProp ?? (globalConfig ? {
4039
- projectId: globalConfig.projectId,
4040
- apiBaseUrl: globalConfig.apiBaseUrl,
4041
- apiKey: globalConfig.apiKey,
4042
- permissions: globalConfig.permissions,
4043
- debug: globalConfig.debug,
4044
- appVersion: globalConfig.appVersion,
4045
- mcpVersion: globalConfig.mcpVersion,
4046
- fetchPersonalization: globalConfig.fetchPersonalization,
4047
- suppressNativeConfirm: globalConfig.suppressNativeConfirm
4048
- } : null);
4049
- if (!config?.projectId) {
4050
- throw new Error(
4051
- "AppilotsProvider: No config found. Add withAppilots() to your metro.config.js and create a .appilotsrc file, or pass a config prop directly."
4052
- );
4870
+ function walkSubtree(root, detectLists, startInModal = false) {
4871
+ const result = {
4872
+ texts: [],
4873
+ inputs: [],
4874
+ buttons: [],
4875
+ toggles: [],
4876
+ sliders: [],
4877
+ visitedFibers: 0,
4878
+ skippedHidden: 0,
4879
+ loading: false,
4880
+ modalOpen: false,
4881
+ lists: [],
4882
+ detectors: emptyDetectorCounts()
4883
+ };
4884
+ if (!root) return result;
4885
+ const visited = /* @__PURE__ */ new WeakSet();
4886
+ const stack = [];
4887
+ if (root.child) stack.push({ fiber: root.child, inModal: startInModal });
4888
+ while (stack.length > 0) {
4889
+ const { fiber, inModal, ancestorPress } = stack.pop();
4890
+ if (!fiber || visited.has(fiber)) continue;
4891
+ visited.add(fiber);
4892
+ result.visitedFibers++;
4893
+ if (fiber.sibling) stack.push({ fiber: fiber.sibling, inModal });
4894
+ if (shouldSkipSubtree(fiber)) {
4895
+ result.skippedHidden++;
4896
+ continue;
4897
+ }
4898
+ if (detectLists) {
4899
+ const isAlways = isAlwaysListContainer(fiber);
4900
+ const isHeuristic = !isAlways && isHeuristicListContainer(fiber);
4901
+ if (isAlways || isHeuristic) {
4902
+ result.detectors.listContainers++;
4903
+ const minItems = isAlways ? 1 : 2;
4904
+ const items = findListItems(fiber, minItems, isAlways);
4905
+ if (items && items.length >= minItems) {
4906
+ const containerType = getTypeName(fiber.type) ?? getTypeName(fiber.elementType) ?? "List";
4907
+ const listIndex = result.lists.length;
4908
+ const itemSnapshots = items.map((itemFiber, i) => {
4909
+ const itemResult = walkSubtree(itemFiber, false, inModal);
4910
+ result.visitedFibers += itemResult.visitedFibers;
4911
+ result.skippedHidden += itemResult.skippedHidden;
4912
+ addDetectorCounts(result.detectors, itemResult.detectors);
4913
+ if (itemResult.loading) result.loading = true;
4914
+ if (itemResult.modalOpen) result.modalOpen = true;
4915
+ const reactKey = typeof itemFiber.key === "string" || typeof itemFiber.key === "number" ? String(itemFiber.key) : typeof itemFiber.memoizedProps?.__appilotsItemKey === "string" || typeof itemFiber.memoizedProps?.__appilotsItemKey === "number" ? String(itemFiber.memoizedProps.__appilotsItemKey) : void 0;
4916
+ const itemKey = typeof itemFiber.memoizedProps?.__appilotsItemKey === "string" || typeof itemFiber.memoizedProps?.__appilotsItemKey === "number" ? String(itemFiber.memoizedProps.__appilotsItemKey) : void 0;
4917
+ const dataIndex = numberProp(itemFiber.memoizedProps, "__appilotsItemIndex");
4918
+ const syntheticId = `list-${listIndex}-item-${i + 1}`;
4919
+ return {
4920
+ index: i + 1,
4921
+ dataIndex,
4922
+ reactKey,
4923
+ itemKey,
4924
+ syntheticId,
4925
+ texts: dedupeStringsInternal(itemResult.texts),
4926
+ // Scope the row's ordinals by its own synthetic id, so an
4927
+ // unnamed control in row 3 cannot collide with one in row 1
4928
+ // or with a screen-level one.
4929
+ buttons: dedupeButtonsInternal(itemResult.buttons, `${syntheticId}-`),
4930
+ inputs: dedupeInputsInternal(itemResult.inputs, `${syntheticId}-`),
4931
+ toggles: dedupeTogglesInternal(itemResult.toggles, `${syntheticId}-`)
4932
+ };
4933
+ });
4934
+ const meta2 = listMetadataFromProps(fiber.memoizedProps);
4935
+ result.lists.push({
4936
+ index: listIndex,
4937
+ id: meta2.id,
4938
+ containerType,
4939
+ source: meta2.source ?? "fiber",
4940
+ itemCount: meta2.itemCount,
4941
+ visibleItemCount: itemSnapshots.length,
4942
+ refreshing: meta2.refreshing,
4943
+ empty: meta2.empty,
4944
+ label: meta2.label,
4945
+ items: itemSnapshots
4946
+ });
4947
+ for (const modalFiber of collectVisibleModalFibers(fiber, items)) {
4948
+ result.modalOpen = true;
4949
+ if (modalFiber.child) stack.push({ fiber: modalFiber.child, inModal: true });
4950
+ }
4951
+ continue;
4952
+ }
4953
+ const meta = listMetadataFromProps(fiber.memoizedProps);
4954
+ const declaresList = meta.itemCount !== void 0 || meta.refreshing || meta.empty || !!explicitListIdFromProps(fiber.memoizedProps);
4955
+ if (declaresList) {
4956
+ const containerType = getTypeName(fiber.type) ?? getTypeName(fiber.elementType) ?? "List";
4957
+ result.lists.push({
4958
+ index: result.lists.length,
4959
+ id: meta.id,
4960
+ containerType,
4961
+ source: meta.source ?? "auto-tracked",
4962
+ itemCount: meta.itemCount,
4963
+ visibleItemCount: 0,
4964
+ refreshing: meta.refreshing,
4965
+ empty: meta.empty,
4966
+ label: meta.label,
4967
+ items: []
4968
+ });
4969
+ for (const modalFiber of collectVisibleModalFibers(fiber)) {
4970
+ result.modalOpen = true;
4971
+ if (modalFiber.child) stack.push({ fiber: modalFiber.child, inModal: true });
4972
+ }
4973
+ continue;
4974
+ }
4975
+ }
4976
+ }
4977
+ const props = fiber.memoizedProps ?? {};
4978
+ if (matchesName(fiber, "Text")) {
4979
+ result.detectors.Text++;
4980
+ const text = extractTextFromChildren(props.children);
4981
+ if (text && text.trim().length > 0) {
4982
+ result.texts.push(text.trim());
4983
+ }
4984
+ } else if (matchesName(fiber, "TextInput")) {
4985
+ result.detectors.TextInput++;
4986
+ if (props.__appilotsInternal !== true) {
4987
+ const isSecure = !!props.secureTextEntry || isAppilotsSensitiveMarked(props);
4988
+ const rawValue = typeof props.value === "string" ? props.value : void 0;
4989
+ const safeValue = isSecure ? rawValue && rawValue.length > 0 ? "<hidden>" : void 0 : rawValue;
4990
+ const identity = resolveIdentity(props, inferLabel(props));
4991
+ const geom = readGeometry(fiber);
4992
+ result.inputs.push(
4993
+ withRect(
4994
+ {
4995
+ id: identity.id,
4996
+ provenance: identity.provenance,
4997
+ label: inferLabel(props),
4998
+ value: safeValue,
4999
+ placeholder: props.placeholder,
5000
+ editable: props.editable !== false,
5001
+ secure: isSecure,
5002
+ type: inferInputType(props),
5003
+ // A field whose return key submits (#398). On a search box or
5004
+ // a one-field login this IS the submit, and without the flag
5005
+ // the screen looks like a form with no way to send it.
5006
+ ...typeof props.onSubmitEditing === "function" ? { submitsOnReturn: true } : {},
5007
+ ...inModal ? { inModal: true } : {},
5008
+ ...geom.fields
5009
+ },
5010
+ geom
5011
+ )
5012
+ );
5013
+ }
5014
+ } else if (props.accessibilityRole === "adjustable") {
5015
+ result.detectors.adjustable++;
5016
+ if (props.__appilotsInternal !== true) {
5017
+ const av = props.accessibilityValue ?? {};
5018
+ const sliderLabel = props.accessibilityLabel ?? props.testID;
5019
+ const sliderIdentity = resolveIdentity(props, sliderLabel);
5020
+ const geom = readGeometry(fiber);
5021
+ result.sliders.push(
5022
+ withRect(
5023
+ {
5024
+ id: sliderIdentity.id,
5025
+ provenance: sliderIdentity.provenance,
5026
+ label: sliderLabel,
5027
+ value: typeof av.now === "number" ? av.now : void 0,
5028
+ min: typeof av.min === "number" ? av.min : void 0,
5029
+ max: typeof av.max === "number" ? av.max : void 0,
5030
+ ...inModal ? { inModal: true } : {},
5031
+ ...geom.fields
5032
+ },
5033
+ geom
5034
+ )
5035
+ );
5036
+ }
5037
+ } else if (isSwitchLike(fiber, props)) {
5038
+ result.detectors.Switch++;
5039
+ if (props.__appilotsInternal !== true) {
5040
+ const label = props.accessibilityLabel ?? props.testID ?? nearbyLabelForSwitch(fiber);
5041
+ const toggleIdentity = resolveIdentity(props, label);
5042
+ const geom = readGeometry(fiber);
5043
+ result.toggles.push(
5044
+ withRect(
5045
+ {
5046
+ // Same fallback the branch below has always had for buttons.
5047
+ // Without it every unnamed switch arrived as `id: undefined`
5048
+ // and dedupeToggles merged them all into one (#385).
5049
+ id: toggleIdentity.id,
5050
+ provenance: toggleIdentity.provenance,
5051
+ label,
5052
+ value: switchValue(props),
5053
+ ...inModal ? { inModal: true } : {},
5054
+ ...geom.fields
5055
+ },
5056
+ geom
5057
+ )
5058
+ );
5059
+ }
5060
+ continue;
5061
+ } else if (isPressableTarget(fiber)) {
5062
+ result.detectors.pressable++;
5063
+ const isRepeatOfAncestor = typeof props.onPress === "function" && props.onPress === ancestorPress;
5064
+ if (props.__appilotsInternal !== true && !isRepeatOfAncestor) {
5065
+ const label = findChildText(fiber);
5066
+ const state = props.accessibilityState ?? {};
5067
+ const on = state.checked === true || state.selected === true;
5068
+ const buttonIdentity = resolveIdentity(props, label);
5069
+ const geom = readGeometry(fiber);
5070
+ result.buttons.push(
5071
+ withRect(
5072
+ {
5073
+ id: buttonIdentity.id,
5074
+ provenance: buttonIdentity.provenance,
5075
+ label,
5076
+ disabled: !!props.disabled,
5077
+ ...on ? { selected: true } : {},
5078
+ ...inModal ? { inModal: true } : {},
5079
+ ...geom.fields
5080
+ },
5081
+ geom
5082
+ )
5083
+ );
5084
+ }
5085
+ if (fiber.child) {
5086
+ stack.push({
5087
+ fiber: fiber.child,
5088
+ inModal,
5089
+ // Children of this pressable inherit its handler, so a nested
5090
+ // fiber forwarding the SAME `onPress` is recognized as a repeat.
5091
+ ancestorPress: typeof props.onPress === "function" ? props.onPress : ancestorPress
5092
+ });
5093
+ }
5094
+ continue;
5095
+ } else if (matchesName(fiber, "ActivityIndicator")) {
5096
+ result.detectors.ActivityIndicator++;
5097
+ result.loading = true;
5098
+ } else if (isModalFiber(fiber)) {
5099
+ result.detectors.Modal++;
5100
+ result.modalOpen = true;
5101
+ if (fiber.child) stack.push({ fiber: fiber.child, inModal: true });
5102
+ continue;
5103
+ }
5104
+ if (fiber.child) stack.push({ fiber: fiber.child, inModal });
4053
5105
  }
4054
- React2.useEffect(() => {
4055
- setAppilotsDebugEnabled(config.debug === true);
4056
- }, [config.debug]);
4057
- const listenersRef = React2.useRef(/* @__PURE__ */ new Set());
4058
- const subscribe = React2.useCallback((handler) => {
4059
- listenersRef.current.add(handler);
4060
- return () => {
4061
- listenersRef.current.delete(handler);
4062
- };
4063
- }, []);
4064
- const emit = React2.useCallback((event) => {
4065
- recordAppilotsEventTrace(event);
4066
- listenersRef.current.forEach((handler) => handler(event));
4067
- }, []);
4068
- const handleIntrospectionUnavailable = React2.useCallback(
4069
- ({ reactVersion }) => {
4070
- emit({
4071
- type: "sdk:introspection:unavailable",
4072
- timestamp: Date.now(),
4073
- data: { reason: "sentinel-missing-internals", reactVersion }
4074
- });
4075
- },
4076
- [emit]
4077
- );
4078
- const client = React2.useMemo(
4079
- () => externalClient ?? new AppilotsClient({
4080
- projectId: config.projectId,
4081
- apiBaseUrl: config.apiBaseUrl,
4082
- apiKey: config.apiKey,
4083
- debug: config.debug,
4084
- appVersion: config.appVersion,
4085
- mcpVersion: config.mcpVersion,
4086
- // This package's published version, so the server knows which
4087
- // SDK is actually in the field (issue #312).
4088
- sdkVersion: SDK_VERSION2,
4089
- user: config.user,
4090
- // Only reports a FAILED reading; the happy path sends nothing.
4091
- introspectionReporter: getIntrospectionDiagnostics
4092
- }),
4093
- [config.projectId, config.apiBaseUrl, config.apiKey, config.debug, config.appVersion, config.mcpVersion, config.user, externalClient]
5106
+ return result;
5107
+ }
5108
+ function walkFiber(root) {
5109
+ const snapshot = {
5110
+ route: getCurrentScreen(),
5111
+ texts: [],
5112
+ inputs: [],
5113
+ buttons: [],
5114
+ toggles: [],
5115
+ // Sliders come from the ComponentRegistry (useAppilotsSlider) —
5116
+ // captureSnapshot merges them so observation only ever advertises
5117
+ // sliders the executor can actually set (min/max/step + setValue).
5118
+ sliders: [],
5119
+ loading: false,
5120
+ modalOpen: false,
5121
+ lists: [],
5122
+ choiceGroups: [],
5123
+ elements: [],
5124
+ stats: { visitedFibers: 0, skippedHidden: 0 }
5125
+ };
5126
+ if (!root) return snapshot;
5127
+ const sub = walkSubtree(root, true);
5128
+ snapshot.texts = dedupeStringsInternal(sub.texts);
5129
+ snapshot.inputs = dedupeInputsInternal(sub.inputs);
5130
+ snapshot.buttons = dedupeButtonsInternal(sub.buttons);
5131
+ snapshot.toggles = dedupeTogglesInternal(sub.toggles);
5132
+ snapshot.sliders = dedupeSliders(sub.sliders);
5133
+ snapshot.loading = sub.loading;
5134
+ snapshot.modalOpen = sub.modalOpen;
5135
+ snapshot.lists = sub.lists;
5136
+ snapshot.choiceGroups = deriveChoiceGroups(snapshot);
5137
+ snapshot.stats.visitedFibers = sub.visitedFibers;
5138
+ snapshot.stats.skippedHidden = sub.skippedHidden;
5139
+ snapshot.stats.identity = tallyIdentity(snapshot);
5140
+ _lastWalk = {
5141
+ visitedFibers: sub.visitedFibers,
5142
+ skippedHidden: sub.skippedHidden,
5143
+ detectors: sub.detectors
5144
+ };
5145
+ return snapshot;
5146
+ }
5147
+ function tallyIdentity(snapshot) {
5148
+ const tally = { declared: 0, derived: 0, positional: 0 };
5149
+ const count = (entries2) => {
5150
+ for (const entry of entries2) {
5151
+ if (entry.provenance) tally[entry.provenance] += 1;
5152
+ }
5153
+ };
5154
+ count(snapshot.inputs);
5155
+ count(snapshot.buttons);
5156
+ count(snapshot.toggles);
5157
+ count(snapshot.sliders);
5158
+ for (const list of snapshot.lists) {
5159
+ for (const item of list.items) {
5160
+ count(item.buttons);
5161
+ count(item.inputs);
5162
+ count(item.toggles);
5163
+ }
5164
+ }
5165
+ return tally;
5166
+ }
5167
+ function dedupeButtonsInternal(buttons, scope = "") {
5168
+ return dedupeButtons(buttons, scope);
5169
+ }
5170
+ function dedupeInputsInternal(inputs, scope = "") {
5171
+ return dedupeInputs(inputs, scope);
5172
+ }
5173
+ function dedupeTogglesInternal(toggles, scope = "") {
5174
+ return dedupeToggles(toggles, scope);
5175
+ }
5176
+ function dedupeStringsInternal(strings) {
5177
+ return dedupeStrings(strings);
5178
+ }
5179
+ function isCommandLikeLabel(label) {
5180
+ if (!label) return true;
5181
+ const normalized = labelToId(label);
5182
+ if (!normalized) return true;
5183
+ return /^(proximo|prox|next|continuar|continue|voltar|back|confirmar|confirm|cancelar|cancel|salvar|save|enviar|submit|ok|limpar|clear|fechar|close|adicionar|add|novo|nova|new)$/i.test(
5184
+ normalized
4094
5185
  );
4095
- const fetchPersonalizationEnabled = config.fetchPersonalization !== false;
4096
- const [remotePersonalization, setRemotePersonalization] = React2.useState(null);
4097
- React2.useEffect(() => {
4098
- if (!fetchPersonalizationEnabled) return;
4099
- let cancelled = false;
4100
- client.getPersonalization().then((data) => {
4101
- if (!cancelled) setRemotePersonalization(data);
4102
- }).catch(() => {
5186
+ }
5187
+ function deriveChoiceGroups(snapshot) {
5188
+ const groups = [];
5189
+ for (const list of snapshot.lists) {
5190
+ const options = list.items.filter((item) => item.texts.length > 0 || item.buttons.length > 0).map((item, i) => {
5191
+ const texts = item.texts.length > 0 ? item.texts : item.buttons.map((b) => b.label ?? b.id ?? "").filter((text) => text.length > 0);
5192
+ return {
5193
+ index: i + 1,
5194
+ syntheticId: `${list.id ?? `list-${list.index}`}-choice-${i + 1}`,
5195
+ targetId: item.syntheticId,
5196
+ label: texts.slice(0, 2).join(" \xB7 "),
5197
+ texts,
5198
+ disabled: false
5199
+ };
4103
5200
  });
4104
- return () => {
4105
- cancelled = true;
5201
+ if (options.length > 0) {
5202
+ groups.push({
5203
+ index: groups.length,
5204
+ id: list.id,
5205
+ label: list.label,
5206
+ source: "list",
5207
+ options
5208
+ });
5209
+ }
5210
+ }
5211
+ const buttonOptions = snapshot.buttons.filter((button) => !button.disabled).filter((button) => isReferenceable(button.label ?? button.id)).filter((button) => !isCommandLikeLabel(button.label ?? button.id)).map((button, i) => {
5212
+ const label = button.label ?? button.id ?? "";
5213
+ return {
5214
+ index: i + 1,
5215
+ syntheticId: `choice-buttons-option-${i + 1}`,
5216
+ targetId: button.id ?? button.label,
5217
+ label,
5218
+ texts: [label],
5219
+ selected: !!button.selected,
5220
+ disabled: !!button.disabled
4106
5221
  };
4107
- }, [client, fetchPersonalizationEnabled]);
4108
- const value = React2.useMemo(
4109
- () => ({ config, client, subscribe, emit, remotePersonalization }),
4110
- [config, client, subscribe, emit, remotePersonalization]
4111
- );
4112
- return /* @__PURE__ */ React2__default.default.createElement(AppilotsContext.Provider, { value }, /* @__PURE__ */ React2__default.default.createElement(AppilotsFiberRoot, { onUnavailable: handleIntrospectionUnavailable }, children));
5222
+ });
5223
+ if (buttonOptions.length >= 2) {
5224
+ groups.push({
5225
+ index: groups.length,
5226
+ id: "visible-options",
5227
+ label: "Visible options",
5228
+ source: "buttons",
5229
+ options: buttonOptions
5230
+ });
5231
+ }
5232
+ return groups;
4113
5233
  }
4114
- function useAppilotsContext() {
4115
- const context = React2.useContext(AppilotsContext);
4116
- if (!context) {
4117
- throw new Error("useAppilotsContext must be used within an <AppilotsProvider>");
5234
+ function positionalId(scope, kind, ordinal) {
5235
+ return `${scope}${kind}-${ordinal}`;
5236
+ }
5237
+ function dedupeButtons(buttons, scope = "") {
5238
+ const seen = /* @__PURE__ */ new Set();
5239
+ const out = [];
5240
+ let unnamed = 0;
5241
+ for (const b of buttons) {
5242
+ const idTrim = b.id?.trim();
5243
+ const labelTrim = b.label?.trim();
5244
+ if (!idTrim && !isReferenceable(labelTrim)) {
5245
+ unnamed += 1;
5246
+ out.push({ ...b, id: positionalId(scope, "btn", unnamed), provenance: "positional" });
5247
+ continue;
5248
+ }
5249
+ const key = `${idTrim ?? ""}|${labelTrim ?? ""}|${b.disabled ? 1 : 0}|${b.inModal ? 1 : 0}|${b.selected ? 1 : 0}`;
5250
+ if (seen.has(key)) continue;
5251
+ seen.add(key);
5252
+ out.push(b);
4118
5253
  }
4119
- return context;
5254
+ return out;
5255
+ }
5256
+ function dedupeInputs(inputs, scope = "") {
5257
+ const seen = /* @__PURE__ */ new Set();
5258
+ const out = [];
5259
+ let unnamed = 0;
5260
+ for (const i of inputs) {
5261
+ if (!i.id?.trim() && !i.label?.trim() && !i.placeholder?.trim()) {
5262
+ unnamed += 1;
5263
+ out.push({ ...i, id: positionalId(scope, "input", unnamed), provenance: "positional" });
5264
+ continue;
5265
+ }
5266
+ const key = `${i.id ?? ""}|${i.label ?? ""}|${i.placeholder ?? ""}|${i.inModal ? 1 : 0}`;
5267
+ if (seen.has(key)) continue;
5268
+ seen.add(key);
5269
+ out.push(i);
5270
+ }
5271
+ return out;
5272
+ }
5273
+ function dedupeSliders(sliders, scope = "") {
5274
+ const seen = /* @__PURE__ */ new Set();
5275
+ const out = [];
5276
+ let unnamed = 0;
5277
+ for (const s of sliders) {
5278
+ if (!s.id?.trim() && !s.label?.trim()) {
5279
+ unnamed += 1;
5280
+ out.push({ ...s, id: positionalId(scope, "slider", unnamed), provenance: "positional" });
5281
+ continue;
5282
+ }
5283
+ const key = `${s.id ?? ""}|${s.label ?? ""}|${s.inModal ? 1 : 0}`;
5284
+ if (seen.has(key)) continue;
5285
+ seen.add(key);
5286
+ out.push(s);
5287
+ }
5288
+ return out;
5289
+ }
5290
+ function dedupeToggles(toggles, scope = "") {
5291
+ let unnamed = 0;
5292
+ return toggles.map((t) => {
5293
+ if (t.id?.trim()) return t;
5294
+ unnamed += 1;
5295
+ return { ...t, id: positionalId(scope, "toggle", unnamed), provenance: "positional" };
5296
+ });
5297
+ }
5298
+ function dedupeStrings(strings) {
5299
+ const seen = /* @__PURE__ */ new Set();
5300
+ const out = [];
5301
+ for (const s of strings) {
5302
+ const trimmed = s.trim();
5303
+ if (!trimmed) continue;
5304
+ if (seen.has(trimmed)) continue;
5305
+ seen.add(trimmed);
5306
+ out.push(trimmed);
5307
+ }
5308
+ return out;
4120
5309
  }
4121
5310
 
4122
5311
  // src/introspection/probeLoadingState.ts
4123
- function getTypeName(type) {
5312
+ function getTypeName2(type) {
4124
5313
  if (!type) return null;
4125
5314
  if (typeof type === "string") return type;
4126
5315
  if (typeof type === "function") return type.displayName ?? type.name ?? null;
4127
5316
  if (typeof type === "object") {
4128
5317
  if (type.displayName) return type.displayName;
4129
5318
  if (type.render) return type.render.displayName ?? type.render.name ?? null;
4130
- if (type.type) return getTypeName(type.type);
5319
+ if (type.type) return getTypeName2(type.type);
4131
5320
  }
4132
5321
  return null;
4133
5322
  }
4134
- function matchesName(fiber, name) {
4135
- return getTypeName(fiber.type) === name || getTypeName(fiber.elementType) === name;
5323
+ function matchesName2(fiber, name) {
5324
+ return getTypeName2(fiber.type) === name || getTypeName2(fiber.elementType) === name;
4136
5325
  }
4137
5326
  var SKELETON_NAME_RE = /skeleton|shimmer|contentloader/i;
4138
5327
  function looksLikeSkeleton(fiber) {
4139
- const name = getTypeName(fiber.type) ?? getTypeName(fiber.elementType);
5328
+ const name = getTypeName2(fiber.type) ?? getTypeName2(fiber.elementType);
4140
5329
  return !!name && SKELETON_NAME_RE.test(name);
4141
5330
  }
4142
- var SELF_REFERENTIAL_COMPONENTS = /* @__PURE__ */ new Set([
5331
+ var SELF_REFERENTIAL_COMPONENTS2 = /* @__PURE__ */ new Set([
4143
5332
  "AppilotsChat",
4144
5333
  "AppilotsChatInner",
4145
5334
  "ActionBreadcrumb",
4146
5335
  "ConfirmDialog"
4147
5336
  ]);
4148
- function shouldSkipSubtree(fiber) {
5337
+ function shouldSkipSubtree2(fiber) {
4149
5338
  const props = fiber.memoizedProps;
4150
5339
  if (!props) return false;
4151
- const typeName2 = getTypeName(fiber.type) ?? getTypeName(fiber.elementType);
4152
- if (typeName2 && SELF_REFERENTIAL_COMPONENTS.has(typeName2)) return true;
5340
+ const typeName2 = getTypeName2(fiber.type) ?? getTypeName2(fiber.elementType);
5341
+ if (typeName2 && SELF_REFERENTIAL_COMPONENTS2.has(typeName2)) return true;
4153
5342
  if (props.__appilotsSkip === true || props["data-appilots-skip"] === true || props.appilotsSkip === true) {
4154
5343
  return true;
4155
5344
  }
4156
- if (matchesName(fiber, "Modal") && props.visible === false) return true;
4157
- if (matchesName(fiber, "Screen") && props.activityState === 0) return true;
5345
+ if (isModalFiber(fiber) && props.visible === false) return true;
5346
+ if (matchesName2(fiber, "Screen") && props.activityState === 0) return true;
4158
5347
  const style = props.style;
4159
5348
  if (style) {
4160
5349
  const flat = Array.isArray(style) ? Object.assign({}, ...style.filter(Boolean)) : style;
@@ -4163,7 +5352,7 @@ function shouldSkipSubtree(fiber) {
4163
5352
  }
4164
5353
  return false;
4165
5354
  }
4166
- var TOUCHABLE_NAMES = /* @__PURE__ */ new Set([
5355
+ var TOUCHABLE_NAMES2 = /* @__PURE__ */ new Set([
4167
5356
  "TouchableOpacity",
4168
5357
  "TouchableHighlight",
4169
5358
  "TouchableWithoutFeedback",
@@ -4171,7 +5360,7 @@ var TOUCHABLE_NAMES = /* @__PURE__ */ new Set([
4171
5360
  "Pressable"
4172
5361
  ]);
4173
5362
  function isTouchable(fiber) {
4174
- return TOUCHABLE_NAMES.has(getTypeName(fiber.type) ?? "") || TOUCHABLE_NAMES.has(getTypeName(fiber.elementType) ?? "");
5363
+ return TOUCHABLE_NAMES2.has(getTypeName2(fiber.type) ?? "") || TOUCHABLE_NAMES2.has(getTypeName2(fiber.elementType) ?? "");
4175
5364
  }
4176
5365
  function pickId(props) {
4177
5366
  return props?.testID ?? props?.accessibilityLabel ?? void 0;
@@ -4207,31 +5396,34 @@ function probeLoadingState(pressedComponentId, route) {
4207
5396
  visited.add(fiber);
4208
5397
  result.visitedFibers++;
4209
5398
  if (fiber.sibling) stack.push(fiber.sibling);
4210
- if (shouldSkipSubtree(fiber)) continue;
5399
+ if (shouldSkipSubtree2(fiber)) continue;
4211
5400
  const props = fiber.memoizedProps ?? {};
4212
- if (matchesName(fiber, "ActivityIndicator") || looksLikeSkeleton(fiber)) {
5401
+ if (matchesName2(fiber, "ActivityIndicator") || looksLikeSkeleton(fiber)) {
4213
5402
  result.loading = true;
4214
- } else if (matchesName(fiber, "Modal")) {
5403
+ } else if (isModalFiber(fiber)) {
4215
5404
  result.modalOpen = true;
5405
+ } else if (isSwitchLike(fiber, props)) {
5406
+ const id = pickId(props);
5407
+ toggleSigs.push(`${id ?? "?"}:${switchValue(props) ? 1 : 0}`);
4216
5408
  } else if (isTouchable(fiber)) {
4217
5409
  if (props.__appilotsInternal !== true) {
4218
5410
  const id = pickId(props);
4219
5411
  const disabled = !!props.disabled;
4220
- buttonSigs.push(`${id ?? "?"}:${disabled ? 1 : 0}`);
5412
+ const state = props.accessibilityState ?? {};
5413
+ const on = state.checked === true || state.selected === true;
5414
+ buttonSigs.push(`${id ?? "?"}:${disabled ? 1 : 0}:${on ? 1 : 0}`);
4221
5415
  if (pressedComponentId && idMatches(id, pressedComponentId)) {
4222
5416
  result.pressedFound = true;
4223
5417
  if (disabled) result.pressedDisabled = true;
4224
5418
  }
4225
5419
  }
4226
- } else if (matchesName(fiber, "TextInput")) {
5420
+ } else if (matchesName2(fiber, "TextInput")) {
4227
5421
  if (props.__appilotsInternal !== true) {
4228
5422
  const id = pickId(props);
4229
5423
  const editable = props.editable !== false;
4230
- inputSigs.push(`${id ?? "?"}:${editable ? 1 : 0}`);
5424
+ const filled = typeof props.value === "string" && props.value.length > 0;
5425
+ inputSigs.push(`${id ?? "?"}:${editable ? 1 : 0}:${filled ? 1 : 0}`);
4231
5426
  }
4232
- } else if (matchesName(fiber, "Switch")) {
4233
- const id = pickId(props);
4234
- toggleSigs.push(`${id ?? "?"}:${props.value ? 1 : 0}`);
4235
5427
  }
4236
5428
  if (fiber.child) stack.push(fiber.child);
4237
5429
  }
@@ -4242,11 +5434,48 @@ function probeLoadingState(pressedComponentId, route) {
4242
5434
  return result;
4243
5435
  }
4244
5436
 
5437
+ // ../shared/dist/routes/index.mjs
5438
+ var CONTAINER_SUFFIXES = [
5439
+ "screen",
5440
+ "route",
5441
+ "page",
5442
+ "view",
5443
+ "tab",
5444
+ "stack",
5445
+ "drawer",
5446
+ "navigator"
5447
+ ];
5448
+ var CONTENT_SUFFIXES = ["list", "details", "detail", "create", "edit", "new", "form", "index"];
5449
+ var MIN_STEM_LENGTH = 3;
5450
+ function fold(name) {
5451
+ return name.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "");
5452
+ }
5453
+ function stripSuffixes(folded, suffixes) {
5454
+ for (const suffix of suffixes) {
5455
+ if (!folded.endsWith(suffix)) continue;
5456
+ const stem = folded.slice(0, -suffix.length);
5457
+ if (stem.length >= MIN_STEM_LENGTH) return stem;
5458
+ }
5459
+ return folded;
5460
+ }
5461
+ function normalizeRouteFamily(name) {
5462
+ const all = [...CONTAINER_SUFFIXES, ...CONTENT_SUFFIXES];
5463
+ let stem = stripSuffixes(stripSuffixes(fold(name), all), all);
5464
+ if (stem.endsWith("s") && stem.length - 1 >= MIN_STEM_LENGTH) stem = stem.slice(0, -1);
5465
+ return stem;
5466
+ }
5467
+ function routesBelongToSameFeature(a, b) {
5468
+ const na = normalizeRouteFamily(a);
5469
+ const nb = normalizeRouteFamily(b);
5470
+ if (!na || !nb) return false;
5471
+ return na === nb;
5472
+ }
5473
+
4245
5474
  // src/navigation/navigationState.ts
4246
5475
  var _currentScreen = null;
4247
5476
  var _navigationRef = null;
4248
5477
  function setCurrentScreen(screen) {
4249
- console.log(`[Appilots] setCurrentScreen: "${_currentScreen}" \u2192 "${screen}"`);
5478
+ appilotsDebugLog(`setCurrentScreen: "${_currentScreen}" \u2192 "${screen}"`);
4250
5479
  _currentScreen = screen;
4251
5480
  }
4252
5481
  function getCurrentScreen() {
@@ -4255,8 +5484,8 @@ function getCurrentScreen() {
4255
5484
  const deepest = activePath[activePath.length - 1] ?? null;
4256
5485
  if (deepest && deepest !== _currentScreen) {
4257
5486
  if (_currentScreen) {
4258
- console.log(
4259
- `[Appilots] getCurrentScreen: active path differs from cached \u2014 deepest="${deepest}", cached="${_currentScreen}". Updating cache.`
5487
+ appilotsDebugLog(
5488
+ `getCurrentScreen: active path differs from cached \u2014 deepest="${deepest}", cached="${_currentScreen}". Updating cache.`
4260
5489
  );
4261
5490
  }
4262
5491
  _currentScreen = deepest;
@@ -4267,7 +5496,9 @@ function getCurrentScreen() {
4267
5496
  const liveScreen = getActiveRouteFromRef(_navigationRef);
4268
5497
  if (liveScreen) {
4269
5498
  if (liveScreen !== _currentScreen) {
4270
- console.log(`[Appilots] getCurrentScreen: live reading differs from cached \u2014 live="${liveScreen}", cached="${_currentScreen}". Updating cache.`);
5499
+ appilotsDebugLog(
5500
+ `getCurrentScreen: live reading differs from cached \u2014 live="${liveScreen}", cached="${_currentScreen}". Updating cache.`
5501
+ );
4271
5502
  _currentScreen = liveScreen;
4272
5503
  }
4273
5504
  return liveScreen;
@@ -4311,7 +5542,9 @@ function getActiveRouteNames() {
4311
5542
  function routeNamesAtState(state) {
4312
5543
  if (!state) return [];
4313
5544
  if (Array.isArray(state.routeNames)) {
4314
- return state.routeNames.filter((name) => typeof name === "string" && name.length > 0);
5545
+ return state.routeNames.filter(
5546
+ (name) => typeof name === "string" && name.length > 0
5547
+ );
4315
5548
  }
4316
5549
  if (Array.isArray(state.routes)) {
4317
5550
  return state.routes.map((route) => route?.name).filter((name) => typeof name === "string" && name.length > 0);
@@ -4428,16 +5661,6 @@ function getActiveRouteFromRef(navRef) {
4428
5661
  }
4429
5662
  }
4430
5663
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
4431
- function normalizeRouteFamily(name) {
4432
- return name.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/(tab|list|screen|details?|navigator)$/i, "");
4433
- }
4434
- function routesBelongToSameFeature(a, b) {
4435
- const na = normalizeRouteFamily(a);
4436
- const nb = normalizeRouteFamily(b);
4437
- if (!na || !nb) return false;
4438
- if (na === nb) return true;
4439
- return na.startsWith(nb) || nb.startsWith(na);
4440
- }
4441
5664
  function navigationTargetReached(target, fromScreen) {
4442
5665
  const cur = getCurrentScreen();
4443
5666
  const path = getActiveRouteNames();
@@ -4613,6 +5836,365 @@ async function waitForLoadingSettle(opts = {}) {
4613
5836
  };
4614
5837
  }
4615
5838
 
5839
+ // src/version.ts
5840
+ var SDK_VERSION2 = "0.11.0";
5841
+ var FiberSentinel = class extends React2__default.default.Component {
5842
+ componentDidMount() {
5843
+ const fiber = this._reactInternals ?? this._reactInternalFiber ?? null;
5844
+ if (!fiber) {
5845
+ const reactVersion = React2__default.default.version ?? null;
5846
+ appilotsDebugWarn(
5847
+ `FiberSentinel: this._reactInternals is undefined; snapshot capture will fall back to render-time owner. React version may be incompatible (react=${reactVersion}).`
5848
+ );
5849
+ recordIntrospectionFailure("sentinel-missing-internals", reactVersion);
5850
+ this.props.onUnavailable?.({ reactVersion });
5851
+ return;
5852
+ }
5853
+ let top = fiber;
5854
+ while (top.return) top = top.return;
5855
+ setFiberRoot(top);
5856
+ }
5857
+ render() {
5858
+ return this.props.children ?? null;
5859
+ }
5860
+ };
5861
+ function AppilotsFiberRoot({
5862
+ children,
5863
+ onUnavailable
5864
+ }) {
5865
+ const Internals = React2__default.default.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED_IN_THIS_VERSION_OF_REACT;
5866
+ const ownerFiber = Internals?.ReactCurrentOwner?.current;
5867
+ if (ownerFiber && !getFiberRoot()) {
5868
+ setFiberRoot(ownerFiber);
5869
+ }
5870
+ return /* @__PURE__ */ React2__default.default.createElement(FiberSentinel, { onUnavailable }, children);
5871
+ }
5872
+
5873
+ // src/debug/logger.ts
5874
+ setAppilotsDebugResolver(() => getGlobalConfig()?.debug === true);
5875
+ function setAppilotsDebugEnabled2(enabled) {
5876
+ setAppilotsDebugEnabled(enabled);
5877
+ }
5878
+
5879
+ // src/platform/nativeDialogTracking.ts
5880
+ var DEFAULT_BUTTON_LABEL = "OK";
5881
+ var ANSWERED_DIALOG_MEMORY_MS = 15e3;
5882
+ var _open = null;
5883
+ var _lastAnswered = null;
5884
+ var _counter = 0;
5885
+ var _installed = null;
5886
+ function getOpenNativeDialog() {
5887
+ return _open;
5888
+ }
5889
+ function getLastAnsweredNativeDialog() {
5890
+ if (!_lastAnswered) return null;
5891
+ if (Date.now() - _lastAnswered.at > ANSWERED_DIALOG_MEMORY_MS) return null;
5892
+ return _lastAnswered;
5893
+ }
5894
+ function labelOf(button, index) {
5895
+ const text = typeof button?.text === "string" ? button.text.trim() : "";
5896
+ if (text.length > 0) return text;
5897
+ return index === 0 ? DEFAULT_BUTTON_LABEL : `Button ${index + 1}`;
5898
+ }
5899
+ function installNativeDialogTracking(host) {
5900
+ if (_installed) return;
5901
+ const original = host.alert;
5902
+ const patched = (title, message, buttons, options) => {
5903
+ const id = `native-dialog-${_counter += 1}`;
5904
+ const source = Array.isArray(buttons) && buttons.length > 0 ? buttons : [{ text: DEFAULT_BUTTON_LABEL }];
5905
+ const close = (dialogId, answeredWith) => {
5906
+ if (_open?.id !== dialogId) return;
5907
+ _lastAnswered = {
5908
+ id: dialogId,
5909
+ ...tracked.title ? { title: tracked.title } : {},
5910
+ buttons: tracked.buttons.map((button) => button.label),
5911
+ ...answeredWith ? { answeredWith } : {},
5912
+ at: Date.now()
5913
+ };
5914
+ _open = null;
5915
+ };
5916
+ const tracked = {
5917
+ id,
5918
+ title: typeof title === "string" && title.trim().length > 0 ? title.trim() : void 0,
5919
+ message: typeof message === "string" && message.trim().length > 0 ? message.trim() : void 0,
5920
+ buttons: []
5921
+ };
5922
+ const wrapped = source.map((button, index) => ({
5923
+ ...button,
5924
+ onPress: (value) => {
5925
+ close(id, labelOf(button, index));
5926
+ button.onPress?.(value);
5927
+ }
5928
+ }));
5929
+ tracked.buttons = source.map((button, index) => ({
5930
+ label: labelOf(button, index),
5931
+ ...button.style ? { style: button.style } : {},
5932
+ press: () => {
5933
+ wrapped[index]?.onPress?.();
5934
+ }
5935
+ }));
5936
+ _open = tracked;
5937
+ const opts = options;
5938
+ const patchedOptions = opts && typeof opts === "object" ? {
5939
+ ...opts,
5940
+ onDismiss: () => {
5941
+ close(id);
5942
+ opts.onDismiss?.();
5943
+ }
5944
+ } : options;
5945
+ return original(title, message, wrapped, patchedOptions);
5946
+ };
5947
+ host.alert = patched;
5948
+ _installed = { host, original, patched };
5949
+ }
5950
+ function uninstallNativeDialogTracking() {
5951
+ if (!_installed) return;
5952
+ if (_installed.host.alert === _installed.patched) {
5953
+ _installed.host.alert = _installed.original;
5954
+ }
5955
+ _installed = null;
5956
+ _open = null;
5957
+ _lastAnswered = null;
5958
+ }
5959
+
5960
+ // src/introspection/nameMangling.ts
5961
+ var _lastProbe = null;
5962
+ function getNameManglingProbe() {
5963
+ return _lastProbe;
5964
+ }
5965
+ function probeNameMangling(components) {
5966
+ const observed = {};
5967
+ let mangled = false;
5968
+ for (const [expected, component] of Object.entries(components)) {
5969
+ if (component === void 0) continue;
5970
+ const actual = getTypeName(component) ?? null;
5971
+ observed[expected] = actual;
5972
+ if (actual !== expected) mangled = true;
5973
+ }
5974
+ _lastProbe = { mangled, observed };
5975
+ if (mangled) {
5976
+ recordIntrospectionFailure("names-mangled", null);
5977
+ console.warn(
5978
+ "[Appilots] This bundle renamed React Native components: " + Object.entries(observed).filter(([expectedName, actual]) => actual !== expectedName).map(([expectedName, actual]) => `${expectedName} \u2192 ${actual ?? "unknown"}`).join(", ") + ". Detection falls back to accessibility roles and component shape, which covers switches, modals and lists but is less precise. To restore names, keep `keep_fnames`/`keep_classnames` in your Metro minifierConfig \u2014 `withAppilots()` sets them for you."
5979
+ );
5980
+ }
5981
+ return _lastProbe;
5982
+ }
5983
+
5984
+ // src/platform/fabricGeometry.ts
5985
+ function readRect(instance) {
5986
+ const candidate = instance;
5987
+ const read = candidate?.getBoundingClientRect ?? candidate?.unstable_getBoundingClientRect;
5988
+ if (typeof read !== "function") return null;
5989
+ const rect = read.call(candidate);
5990
+ if (!rect) return null;
5991
+ return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
5992
+ }
5993
+ function createFabricGeometryHost(dimensions) {
5994
+ const fabric = globalThis.nativeFabricUIManager;
5995
+ if (!fabric) return null;
5996
+ return {
5997
+ get window() {
5998
+ return dimensions.get("window");
5999
+ },
6000
+ rectOf: readRect
6001
+ };
6002
+ }
6003
+ var AppilotsErrorBoundary = class extends React2__default.default.Component {
6004
+ state = { degraded: false, escalate: null };
6005
+ /**
6006
+ * Instance field rather than state: `componentDidCatch` needs to know
6007
+ * whether THIS is the first failure, and by the time it runs
6008
+ * `getDerivedStateFromError` has already flipped `state.degraded`.
6009
+ */
6010
+ hasDegradedOnce = false;
6011
+ static getDerivedStateFromError() {
6012
+ return { degraded: true };
6013
+ }
6014
+ componentDidCatch(error, info) {
6015
+ if (this.hasDegradedOnce) {
6016
+ this.setState({ escalate: error ?? new Error("Appilots: re-thrown host render error") });
6017
+ return;
6018
+ }
6019
+ this.hasDegradedOnce = true;
6020
+ console.error(
6021
+ `[Appilots] The Appilots ${this.props.surface} threw while rendering and has been disabled for the rest of this session. Your app keeps running \u2014 this is an Appilots bug, not a bug in your code. Please report it with the stack below: https://github.com/Axtern-Labs/Appilots/issues`,
6022
+ error
6023
+ );
6024
+ try {
6025
+ this.props.onError?.(error, info?.componentStack ?? null);
6026
+ } catch {
6027
+ }
6028
+ }
6029
+ render() {
6030
+ if (this.state.escalate !== null) throw this.state.escalate;
6031
+ if (this.state.degraded) return this.props.fallback;
6032
+ return this.props.children;
6033
+ }
6034
+ };
6035
+
6036
+ // src/context/AppilotsProvider.tsx
6037
+ var AppilotsContext = React2.createContext(null);
6038
+ function createInertClient() {
6039
+ const unavailable = () => Promise.reject(new Error("Appilots is unavailable: the SDK degraded after a render error."));
6040
+ return new Proxy({}, {
6041
+ get: (_target, prop) => {
6042
+ if (prop === "then" || typeof prop === "symbol") return void 0;
6043
+ return unavailable;
6044
+ }
6045
+ });
6046
+ }
6047
+ function createDegradedContext(config) {
6048
+ return {
6049
+ config: config ?? { projectId: "" },
6050
+ client: createInertClient(),
6051
+ subscribe: () => () => {
6052
+ },
6053
+ emit: () => {
6054
+ },
6055
+ remotePersonalization: null,
6056
+ degraded: true
6057
+ };
6058
+ }
6059
+ function AppilotsProvider({ config, children, client }) {
6060
+ return /* @__PURE__ */ React2__default.default.createElement(
6061
+ AppilotsErrorBoundary,
6062
+ {
6063
+ surface: "provider",
6064
+ onError: reportProviderRenderFailure,
6065
+ fallback: /* @__PURE__ */ React2__default.default.createElement(AppilotsContext.Provider, { value: createDegradedContext(config ?? null) }, children)
6066
+ },
6067
+ /* @__PURE__ */ React2__default.default.createElement(AppilotsProviderInner, { config, client }, children)
6068
+ );
6069
+ }
6070
+ function reportProviderRenderFailure() {
6071
+ recordRenderFailure(readReactVersion());
6072
+ }
6073
+ function readReactVersion() {
6074
+ const version = React2__default.default.version;
6075
+ return typeof version === "string" ? version : null;
6076
+ }
6077
+ function AppilotsProviderInner({
6078
+ config: configProp,
6079
+ children,
6080
+ client: externalClient
6081
+ }) {
6082
+ appilotsDebugLog(
6083
+ `AppilotsProvider initializing \u2014 config source: ${configProp ? "prop" : "auto (globalThis.__APPILOTS_RC__)"}`
6084
+ );
6085
+ tryAutoConfig();
6086
+ const globalConfig = getGlobalConfig();
6087
+ appilotsDebugLog(`AppilotsProvider: configProp=${!!configProp}, globalConfig=${!!globalConfig}`);
6088
+ const config = configProp ?? (globalConfig ? {
6089
+ projectId: globalConfig.projectId,
6090
+ apiBaseUrl: globalConfig.apiBaseUrl,
6091
+ apiKey: globalConfig.apiKey,
6092
+ permissions: globalConfig.permissions,
6093
+ debug: globalConfig.debug,
6094
+ appVersion: globalConfig.appVersion,
6095
+ mcpVersion: globalConfig.mcpVersion,
6096
+ fetchPersonalization: globalConfig.fetchPersonalization,
6097
+ suppressNativeConfirm: globalConfig.suppressNativeConfirm
6098
+ } : null);
6099
+ if (!config?.projectId) {
6100
+ throw new Error(
6101
+ "AppilotsProvider: No config found. Add withAppilots() to your metro.config.js and create a .appilotsrc file, or pass a config prop directly."
6102
+ );
6103
+ }
6104
+ React2.useEffect(() => {
6105
+ setAppilotsDebugEnabled2(config.debug === true);
6106
+ }, [config.debug]);
6107
+ React2.useEffect(() => {
6108
+ probeNameMangling({ Switch: reactNative.Switch, Modal: reactNative.Modal, Text: reactNative.Text });
6109
+ }, []);
6110
+ React2.useEffect(() => {
6111
+ setGeometryHost(createFabricGeometryHost(reactNative.Dimensions));
6112
+ return () => setGeometryHost(null);
6113
+ }, []);
6114
+ const trackNativeDialogs = config.trackNativeDialogs !== false;
6115
+ React2.useEffect(() => {
6116
+ if (!trackNativeDialogs) return;
6117
+ installNativeDialogTracking(reactNative.Alert);
6118
+ return () => {
6119
+ uninstallNativeDialogTracking();
6120
+ };
6121
+ }, [trackNativeDialogs]);
6122
+ const listenersRef = React2.useRef(/* @__PURE__ */ new Set());
6123
+ const subscribe = React2.useCallback((handler) => {
6124
+ listenersRef.current.add(handler);
6125
+ return () => {
6126
+ listenersRef.current.delete(handler);
6127
+ };
6128
+ }, []);
6129
+ const emit = React2.useCallback((event) => {
6130
+ recordAppilotsEventTrace(event);
6131
+ listenersRef.current.forEach((handler) => handler(event));
6132
+ }, []);
6133
+ const handleIntrospectionUnavailable = React2.useCallback(
6134
+ ({ reactVersion }) => {
6135
+ emit({
6136
+ type: "sdk:introspection:unavailable",
6137
+ timestamp: Date.now(),
6138
+ data: { reason: "sentinel-missing-internals", reactVersion }
6139
+ });
6140
+ },
6141
+ [emit]
6142
+ );
6143
+ const client = React2.useMemo(
6144
+ () => externalClient ?? new AppilotsClient({
6145
+ projectId: config.projectId,
6146
+ apiBaseUrl: config.apiBaseUrl,
6147
+ apiKey: config.apiKey,
6148
+ debug: config.debug,
6149
+ appVersion: config.appVersion,
6150
+ mcpVersion: config.mcpVersion,
6151
+ // This package's published version, so the server knows which
6152
+ // SDK is actually in the field (issue #312).
6153
+ sdkVersion: SDK_VERSION2,
6154
+ user: config.user,
6155
+ // Only reports a FAILED reading; the happy path sends nothing.
6156
+ introspectionReporter: getIntrospectionDiagnostics
6157
+ }),
6158
+ [
6159
+ config.projectId,
6160
+ config.apiBaseUrl,
6161
+ config.apiKey,
6162
+ config.debug,
6163
+ config.appVersion,
6164
+ config.mcpVersion,
6165
+ config.user,
6166
+ externalClient
6167
+ ]
6168
+ );
6169
+ const fetchPersonalizationEnabled = config.fetchPersonalization !== false;
6170
+ const [remotePersonalization, setRemotePersonalization] = React2.useState(
6171
+ null
6172
+ );
6173
+ React2.useEffect(() => {
6174
+ if (!fetchPersonalizationEnabled) return;
6175
+ let cancelled = false;
6176
+ client.getPersonalization().then((data) => {
6177
+ if (!cancelled) setRemotePersonalization(data);
6178
+ }).catch(() => {
6179
+ });
6180
+ return () => {
6181
+ cancelled = true;
6182
+ };
6183
+ }, [client, fetchPersonalizationEnabled]);
6184
+ const value = React2.useMemo(
6185
+ () => ({ config, client, subscribe, emit, remotePersonalization }),
6186
+ [config, client, subscribe, emit, remotePersonalization]
6187
+ );
6188
+ return /* @__PURE__ */ React2__default.default.createElement(AppilotsContext.Provider, { value }, /* @__PURE__ */ React2__default.default.createElement(AppilotsFiberRoot, { onUnavailable: handleIntrospectionUnavailable }, children));
6189
+ }
6190
+ function useAppilotsContext() {
6191
+ const context = React2.useContext(AppilotsContext);
6192
+ if (!context) {
6193
+ throw new Error("useAppilotsContext must be used within an <AppilotsProvider>");
6194
+ }
6195
+ return context;
6196
+ }
6197
+
4616
6198
  // src/navigation/registerScreen.ts
4617
6199
  var screenRegistry = /* @__PURE__ */ new Map();
4618
6200
  function registerScreen(metadata) {
@@ -4634,9 +6216,12 @@ exports.AppilotsErrorBoundary = AppilotsErrorBoundary;
4634
6216
  exports.AppilotsProvider = AppilotsProvider;
4635
6217
  exports.AppilotsRegistryProvider = AppilotsRegistryProvider;
4636
6218
  exports.ChatSessionMachine = ChatSessionMachine;
6219
+ exports.FORM_VALIDATION_TEXT_RE = FORM_VALIDATION_TEXT_RE;
6220
+ exports.FORM_WRITE_SUCCESS_TEXT_RE = FORM_WRITE_SUCCESS_TEXT_RE;
4637
6221
  exports.OPTIONAL_STEP_AUTOMATION_HINT = OPTIONAL_STEP_AUTOMATION_HINT;
4638
6222
  exports.RateLimitedError = RateLimitedError;
4639
6223
  exports.SDK_VERSION = SDK_VERSION2;
6224
+ exports.TOUCHABLE_NAMES = TOUCHABLE_NAMES;
4640
6225
  exports._patchJsxRuntimes = _patchJsxRuntimes;
4641
6226
  exports.actionPressTargetId = actionPressTargetId;
4642
6227
  exports.appilotsDebugLog = appilotsDebugLog;
@@ -4653,27 +6238,44 @@ exports.defaultDarkTheme = defaultDarkTheme;
4653
6238
  exports.defaultLightTheme = defaultLightTheme;
4654
6239
  exports.deriveInteractionElements = deriveInteractionElements;
4655
6240
  exports.describeAction = describeAction;
6241
+ exports.describeValue = describeValue;
4656
6242
  exports.elementRegistry = elementRegistry;
6243
+ exports.emptyDetectorCounts = emptyDetectorCounts;
4657
6244
  exports.enableAppilotsAutoTracking = enableAppilotsAutoTracking;
6245
+ exports.findListItems = findListItems;
6246
+ exports.foldForCompare = foldForCompare;
6247
+ exports.geometryAvailability = geometryAvailability;
6248
+ exports.geometryStats = geometryStats;
4658
6249
  exports.getActiveRouteNames = getActiveRouteNames;
4659
6250
  exports.getAllScreens = getAllScreens;
4660
6251
  exports.getAppilotsDebugTraces = getAppilotsDebugTraces;
6252
+ exports.getAutoTrackingState = getAutoTrackingState;
4661
6253
  exports.getCurrentScreen = getCurrentScreen;
4662
6254
  exports.getCurrentScreenSignature = getCurrentScreenSignature;
4663
6255
  exports.getDefaultRegistry = getDefaultRegistry;
4664
6256
  exports.getFiberRoot = getFiberRoot;
4665
6257
  exports.getGlobalConfig = getGlobalConfig;
4666
6258
  exports.getIntrospectionDiagnostics = getIntrospectionDiagnostics;
6259
+ exports.getLastAnsweredNativeDialog = getLastAnsweredNativeDialog;
6260
+ exports.getLastWalkDiagnostics = getLastWalkDiagnostics;
6261
+ exports.getNameManglingProbe = getNameManglingProbe;
4667
6262
  exports.getNavigationRef = getNavigationRef;
4668
6263
  exports.getNavigationStateSnapshot = getNavigationStateSnapshot;
6264
+ exports.getOpenNativeDialog = getOpenNativeDialog;
4669
6265
  exports.getScreenMetadata = getScreenMetadata;
6266
+ exports.getTypeName = getTypeName;
4670
6267
  exports.humanizeError = humanizeError;
4671
6268
  exports.idLooselyMatches = idLooselyMatches;
4672
6269
  exports.initAppilots = initAppilots;
6270
+ exports.isAlwaysListContainer = isAlwaysListContainer;
4673
6271
  exports.isAutoTrackingEnabled = isAutoTrackingEnabled;
4674
6272
  exports.isGenericSelectValue = isGenericSelectValue;
6273
+ exports.isHeuristicListContainer = isHeuristicListContainer;
4675
6274
  exports.isListItemPressTarget = isListItemPressTarget;
6275
+ exports.isModalFiber = isModalFiber;
4676
6276
  exports.isOptionalStepAutomationFailure = isOptionalStepAutomationFailure;
6277
+ exports.isReferenceable = isReferenceable;
6278
+ exports.isSwitchLike = isSwitchLike;
4677
6279
  exports.listRegistry = listRegistry;
4678
6280
  exports.looksDestructiveActionLabel = looksDestructiveActionLabel;
4679
6281
  exports.matchScore = matchScore;
@@ -4683,15 +6285,21 @@ exports.parseStableElementId = parseStableElementId;
4683
6285
  exports.probeLoadingState = probeLoadingState;
4684
6286
  exports.recordAppilotsDebugTrace = recordAppilotsDebugTrace;
4685
6287
  exports.recordIntrospectionFailure = recordIntrospectionFailure;
6288
+ exports.rectForNode = rectForNode;
4686
6289
  exports.registerScreen = registerScreen;
4687
6290
  exports.resolveBaseTheme = resolveBaseTheme;
4688
6291
  exports.routesBelongToSameFeature = routesBelongToSameFeature;
4689
6292
  exports.screenDepartedBaseline = screenDepartedBaseline;
4690
6293
  exports.setCurrentScreen = setCurrentScreen;
4691
6294
  exports.setNavigationRef = setNavigationRef;
6295
+ exports.shouldSkipSubtree = shouldSkipSubtree;
4692
6296
  exports.snapshotShowsOpenCreateForm = snapshotShowsOpenCreateForm;
4693
6297
  exports.subscribeAppilotsDebugTraces = subscribeAppilotsDebugTraces;
6298
+ exports.switchValue = switchValue;
6299
+ exports.toWireElement = toWireElement;
6300
+ exports.totalDetectorHits = totalDetectorHits;
4694
6301
  exports.useAppilotsContext = useAppilotsContext;
4695
6302
  exports.useResolvedRegistry = useResolvedRegistry;
4696
6303
  exports.waitForLoadingSettle = waitForLoadingSettle;
4697
6304
  exports.waitForScreenSettle = waitForScreenSettle;
6305
+ exports.walkFiber = walkFiber;