@appilots/sdk 0.11.1 → 0.11.3

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.
@@ -161,7 +161,7 @@ new RegExp(
161
161
  );
162
162
  var CREATE_EDIT_ROUTE_RE = new RegExp(`(${build(CREATE_EDIT_ROUTE)})`, "i");
163
163
 
164
- // ../shared/dist/chunk-43YQ52Q7.mjs
164
+ // ../shared/dist/chunk-SWQOZWHP.mjs
165
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;
166
166
  function isSensitiveFieldName(id, label, placeholder, type) {
167
167
  if (type === "password") return true;
@@ -1370,6 +1370,7 @@ function snapshotHasVisibleSubmitButton(snap) {
1370
1370
  );
1371
1371
  }
1372
1372
  function snapshotHasVisibleValidation(snap) {
1373
+ if ((snap.inputs ?? []).some((input) => input.invalid === true)) return true;
1373
1374
  return (snap.texts ?? []).some(
1374
1375
  (text) => typeof text === "string" && FORM_VALIDATION_TEXT_RE.test(text)
1375
1376
  );
@@ -1515,6 +1516,8 @@ var ChatSessionMachine = class {
1515
1516
  /** Tear down timers and the escalation stream. Safe to call twice. */
1516
1517
  dispose() {
1517
1518
  this.disposed = true;
1519
+ this.abortController?.abort();
1520
+ this.currentTurn = null;
1518
1521
  this.closeEscalationStream();
1519
1522
  }
1520
1523
  setState(partial) {
@@ -1561,7 +1564,7 @@ var ChatSessionMachine = class {
1561
1564
  }
1562
1565
  // ── Escalation: request, stream, and message routing ─────────
1563
1566
  async requestHuman(reason = "user_requested") {
1564
- if (this.state.escalation) return;
1567
+ if (this.disposed || this.state.escalation) return;
1565
1568
  try {
1566
1569
  const lastError = this.state.messages.slice().reverse().find((m) => m.role === "assistant" && m.id.startsWith("error_"));
1567
1570
  const result = await this.deps.client.requestEscalation(reason, lastError?.content);
@@ -1735,7 +1738,7 @@ var ChatSessionMachine = class {
1735
1738
  // ── Continuation: when all current-turn actions finish, ask the
1736
1739
  // model what to do next, given the outcomes + fresh snapshot.
1737
1740
  async runContinuation(turn) {
1738
- if (this.continuationInFlight) return;
1741
+ if (this.disposed || this.continuationInFlight) return;
1739
1742
  this.continuationInFlight = true;
1740
1743
  try {
1741
1744
  this.setState({ isLoading: true, loadingStatusKey: "statusWaitingApp" });
@@ -1758,6 +1761,7 @@ var ChatSessionMachine = class {
1758
1761
  pressedComponentId: pressedId,
1759
1762
  navigateTargetScreen
1760
1763
  });
1764
+ if (this.disposed || this.currentTurn !== turn) return;
1761
1765
  const loadingPending = settle.loadingPending;
1762
1766
  for (const id of settle.toolWithoutEffectIds) toolWithoutEffectIds.add(id);
1763
1767
  const context = this.deps.adapter.buildContext({ loadingPending });
@@ -1823,6 +1827,7 @@ var ChatSessionMachine = class {
1823
1827
  `ChatSessionMachine: running continuation hop=${turn.hop} (${results.filter((r) => r.success).length}/${results.length} succeeded` + (exhausted.length > 0 ? `, ${exhausted.length} recovery-exhausted` : "") + ")"
1824
1828
  );
1825
1829
  const response = await this.deps.client.continueAgent(results, context, turn.hop);
1830
+ if (this.disposed) return;
1826
1831
  this.addAssistantMessage(response.message);
1827
1832
  this.deps.emit({
1828
1833
  type: "agent:message",
@@ -1910,7 +1915,7 @@ var ChatSessionMachine = class {
1910
1915
  }
1911
1916
  // ── Send / cancel / clear ────────────────────────────────────
1912
1917
  async sendMessage(content) {
1913
- if (!content.trim() || this.state.isLoading) return;
1918
+ if (this.disposed || !content.trim() || this.state.isLoading) return;
1914
1919
  if (this.state.escalation) {
1915
1920
  await this.sendEscalationMessage(content.trim());
1916
1921
  return;
@@ -1937,7 +1942,7 @@ var ChatSessionMachine = class {
1937
1942
  const abortController = new AbortController();
1938
1943
  this.abortController = abortController;
1939
1944
  try {
1940
- const context = this.deps.adapter.buildContext();
1945
+ const context = this.deps.adapter.buildContext({ newMission: true });
1941
1946
  appilotsDebugLog(
1942
1947
  `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}}`
1943
1948
  );
@@ -2138,6 +2143,7 @@ var ActionQueueMachine = class {
2138
2143
  processed = /* @__PURE__ */ new Set();
2139
2144
  /** Guards against overlapping sequential runs. */
2140
2145
  draining = false;
2146
+ disposed = false;
2141
2147
  /** Set when new work arrives while a drain is in flight. */
2142
2148
  drainRequested = false;
2143
2149
  /** Set while a drain is queued on the microtask queue. */
@@ -2152,6 +2158,12 @@ var ActionQueueMachine = class {
2152
2158
  this.rejectAction = this.rejectAction.bind(this);
2153
2159
  }
2154
2160
  // ── State plumbing ───────────────────────────────────────────
2161
+ dispose() {
2162
+ this.disposed = true;
2163
+ this.drainRequested = false;
2164
+ this.setActions([]);
2165
+ this.listeners.clear();
2166
+ }
2155
2167
  getState() {
2156
2168
  return this.state;
2157
2169
  }
@@ -2177,7 +2189,7 @@ var ActionQueueMachine = class {
2177
2189
  }
2178
2190
  // ── Event intake: new actions from the agent ─────────────────
2179
2191
  handleEvent(event) {
2180
- if (event.type !== "agent:action:start") return;
2192
+ if (this.disposed || event.type !== "agent:action:start") return;
2181
2193
  const action = event.data?.action;
2182
2194
  if (!action?.id) return;
2183
2195
  appilotsDebugLog(
@@ -2204,7 +2216,7 @@ var ActionQueueMachine = class {
2204
2216
  * batching before its effect ran.)
2205
2217
  */
2206
2218
  scheduleDrain() {
2207
- if (!this.options.autoExecute) return;
2219
+ if (this.disposed || !this.options.autoExecute) return;
2208
2220
  if (this.draining) {
2209
2221
  this.drainRequested = true;
2210
2222
  return;
@@ -2213,7 +2225,7 @@ var ActionQueueMachine = class {
2213
2225
  this.drainScheduled = true;
2214
2226
  queueMicrotask(() => {
2215
2227
  this.drainScheduled = false;
2216
- if (!this.options.autoExecute) return;
2228
+ if (this.disposed || !this.options.autoExecute) return;
2217
2229
  if (this.draining) {
2218
2230
  this.drainRequested = true;
2219
2231
  return;
@@ -2336,6 +2348,7 @@ var ActionQueueMachine = class {
2336
2348
  let preNavScreen = null;
2337
2349
  let preNavSignature = null;
2338
2350
  for (const [index, action] of pending.entries()) {
2351
+ if (this.disposed) return;
2339
2352
  if (action.type === "confirm") {
2340
2353
  continue;
2341
2354
  }
@@ -2455,6 +2468,7 @@ var ActionQueueMachine = class {
2455
2468
  }
2456
2469
  // ── Approve & execute an action ─────────────────────────────
2457
2470
  async approveAction(actionId) {
2471
+ if (this.disposed) return false;
2458
2472
  const action = this.state.actions.find((a) => a.id === actionId);
2459
2473
  if (!action) return false;
2460
2474
  appilotsDebugLog(
@@ -3270,6 +3284,91 @@ function clampSnapshotToWireLimits(snapshot) {
3270
3284
  if (cut.dropped) snapshot.truncated = true;
3271
3285
  return snapshot;
3272
3286
  }
3287
+ var MAX_DELTA_FIELD_IDS = 12;
3288
+ function isFilled(input) {
3289
+ if (typeof input.filled === "boolean") return input.filled;
3290
+ return typeof input.value === "string" && input.value.length > 0;
3291
+ }
3292
+ function fingerprintSnapshot(snap) {
3293
+ const texts = /* @__PURE__ */ new Set();
3294
+ for (const text of snap.texts ?? []) {
3295
+ if (typeof text !== "string") continue;
3296
+ const trimmed = text.trim();
3297
+ if (trimmed) texts.add(trimmed);
3298
+ }
3299
+ let visibleRows = 0;
3300
+ let totalRows = 0;
3301
+ for (const list of snap.lists ?? []) {
3302
+ visibleRows += list.items?.length ?? 0;
3303
+ if (typeof list.itemCount === "number") totalRows += list.itemCount;
3304
+ }
3305
+ const filledDeclaredFields = /* @__PURE__ */ new Set();
3306
+ let anyInvalid = false;
3307
+ for (const input of snap.inputs ?? []) {
3308
+ if (input.invalid === true) anyInvalid = true;
3309
+ if (input.provenance !== "declared") continue;
3310
+ if (!input.id) continue;
3311
+ if (isFilled(input)) filledDeclaredFields.add(input.id);
3312
+ }
3313
+ return {
3314
+ route: snap.route ?? null,
3315
+ modal: snap.modalOpen === true || !!snap.nativeDialog,
3316
+ loading: snap.loading === true,
3317
+ texts,
3318
+ visibleRows,
3319
+ totalRows,
3320
+ filledDeclaredFields,
3321
+ anyInvalid
3322
+ };
3323
+ }
3324
+ function countMissing(from, inSet) {
3325
+ let n = 0;
3326
+ for (const value of from) if (!inSet.has(value)) n += 1;
3327
+ return n;
3328
+ }
3329
+ function namesMissing(from, inSet) {
3330
+ const out = [];
3331
+ for (const value of from) if (!inSet.has(value)) out.push(value);
3332
+ out.sort();
3333
+ return out.slice(0, MAX_DELTA_FIELD_IDS);
3334
+ }
3335
+ function diffFingerprints(prev, next) {
3336
+ const delta = {};
3337
+ if (prev.route !== next.route) delta.routeChanged = true;
3338
+ if (!prev.modal && next.modal) delta.modalOpened = true;
3339
+ if (prev.modal && !next.modal) delta.modalClosed = true;
3340
+ if (!prev.loading && next.loading) delta.loadingStarted = true;
3341
+ if (prev.loading && !next.loading) delta.loadingFinished = true;
3342
+ const added = countMissing(next.texts, prev.texts);
3343
+ const removed = countMissing(prev.texts, next.texts);
3344
+ if (added > 0) delta.textsAdded = added;
3345
+ if (removed > 0) delta.textsRemoved = removed;
3346
+ const visibleRowsDelta = next.visibleRows - prev.visibleRows;
3347
+ if (visibleRowsDelta !== 0) delta.visibleRowsDelta = visibleRowsDelta;
3348
+ const totalRowsDelta = next.totalRows - prev.totalRows;
3349
+ if (totalRowsDelta !== 0) delta.totalRowsDelta = totalRowsDelta;
3350
+ const newlyFilled = namesMissing(next.filledDeclaredFields, prev.filledDeclaredFields);
3351
+ if (newlyFilled.length > 0) delta.fieldsNewlyFilled = newlyFilled;
3352
+ const cleared = namesMissing(prev.filledDeclaredFields, next.filledDeclaredFields);
3353
+ if (cleared.length > 0) delta.fieldsCleared = cleared;
3354
+ if (!prev.anyInvalid && next.anyInvalid) delta.invalidAppeared = true;
3355
+ if (Object.keys(delta).length === 0) delta.unchanged = true;
3356
+ return delta;
3357
+ }
3358
+ function createSnapshotDeltaTracker() {
3359
+ let previous;
3360
+ return {
3361
+ observe(snap) {
3362
+ const next = fingerprintSnapshot(snap);
3363
+ const delta = previous ? diffFingerprints(previous, next) : void 0;
3364
+ previous = next;
3365
+ return delta;
3366
+ },
3367
+ reset() {
3368
+ previous = void 0;
3369
+ }
3370
+ };
3371
+ }
3273
3372
 
3274
3373
  // src/registry/ComponentRegistry.ts
3275
3374
  var ComponentRegistryImpl = class {
@@ -4471,6 +4570,37 @@ function rectForNode(node) {
4471
4570
  return _rectByNode.get(node);
4472
4571
  }
4473
4572
 
4573
+ // src/introspection/focusState.ts
4574
+ var _host2 = null;
4575
+ function setFocusHost(host) {
4576
+ _host2 = host;
4577
+ }
4578
+ var HOST_SEARCH_DEPTH2 = 6;
4579
+ function isFiberFocused(fiber) {
4580
+ const host = _host2;
4581
+ if (!host || !fiber) return void 0;
4582
+ let focused = null;
4583
+ try {
4584
+ focused = host.focusedInstance();
4585
+ } catch {
4586
+ return void 0;
4587
+ }
4588
+ if (!focused) return false;
4589
+ const stack = [{ node: fiber, depth: 0 }];
4590
+ while (stack.length > 0) {
4591
+ const { node, depth } = stack.pop();
4592
+ const current = node;
4593
+ if (!current) continue;
4594
+ if (current.stateNode && current.stateNode === focused) return true;
4595
+ if (depth < HOST_SEARCH_DEPTH2 && current.child) {
4596
+ stack.push({ node: current.child, depth: depth + 1 });
4597
+ const sibling = current.child.sibling;
4598
+ if (sibling) stack.push({ node: sibling, depth: depth + 1 });
4599
+ }
4600
+ }
4601
+ return false;
4602
+ }
4603
+
4474
4604
  // src/introspection/walkFiber.ts
4475
4605
  function getTypeName(type) {
4476
4606
  if (!type) return null;
@@ -4539,6 +4669,71 @@ function isAppilotsSensitiveMarked(props) {
4539
4669
  if (!props) return false;
4540
4670
  return props.__appilotsSensitive === true || props["data-appilots-sensitive"] === true || props.appilotsSensitive === true;
4541
4671
  }
4672
+ function booleanish(value) {
4673
+ if (typeof value === "boolean") return value;
4674
+ if (typeof value === "string") {
4675
+ const normalized = value.trim().toLowerCase();
4676
+ if (normalized === "true") return true;
4677
+ if (normalized === "false") return false;
4678
+ }
4679
+ return void 0;
4680
+ }
4681
+ function firstBooleanish(props, keys) {
4682
+ for (const key of keys) {
4683
+ const resolved = booleanish(props?.[key]);
4684
+ if (resolved !== void 0) return resolved;
4685
+ }
4686
+ return void 0;
4687
+ }
4688
+ function readRequiredProp(props) {
4689
+ const direct = firstBooleanish(props, [
4690
+ "required",
4691
+ "aria-required",
4692
+ "isRequired",
4693
+ "accessibilityRequired"
4694
+ ]);
4695
+ if (direct !== void 0) return direct;
4696
+ return booleanish(props?.accessibilityState?.required);
4697
+ }
4698
+ function readInvalidProp(props) {
4699
+ const direct = firstBooleanish(props, ["invalid", "aria-invalid", "isInvalid", "hasError"]);
4700
+ if (direct !== void 0) return direct;
4701
+ const state = booleanish(props?.accessibilityState?.invalid);
4702
+ if (state !== void 0) return state;
4703
+ for (const key of ["error", "errorMessage", "errorText"]) {
4704
+ const value = props?.[key];
4705
+ const asBoolean = booleanish(value);
4706
+ if (asBoolean !== void 0) return asBoolean;
4707
+ if (typeof value === "string") return value.trim().length > 0;
4708
+ }
4709
+ return void 0;
4710
+ }
4711
+ var WRAPPER_PROP_SEARCH_DEPTH = 3;
4712
+ var WRAPPER_SUBTREE_BUDGET = 64;
4713
+ function wrapsExactlyOneInput(fiber) {
4714
+ let seen = 0;
4715
+ let visited = 0;
4716
+ const stack = [fiber?.child];
4717
+ while (stack.length > 0) {
4718
+ if ((visited += 1) > WRAPPER_SUBTREE_BUDGET) return false;
4719
+ const node = stack.pop();
4720
+ if (!node) continue;
4721
+ if (matchesName(node, "TextInput") && (seen += 1) > 1) return false;
4722
+ if (node.child) stack.push(node.child);
4723
+ if (node.sibling) stack.push(node.sibling);
4724
+ }
4725
+ return seen === 1;
4726
+ }
4727
+ function readWrapperProps(fiber) {
4728
+ let node = fiber?.return;
4729
+ for (let depth = 0; node && depth < WRAPPER_PROP_SEARCH_DEPTH; depth += 1, node = node.return) {
4730
+ const props = node.memoizedProps;
4731
+ if (!props) continue;
4732
+ if (readInvalidProp(props) === void 0 && readRequiredProp(props) === void 0) continue;
4733
+ return wrapsExactlyOneInput(node) ? props : void 0;
4734
+ }
4735
+ return void 0;
4736
+ }
4542
4737
  function stringProp(props, name) {
4543
4738
  const value = props?.[name];
4544
4739
  return typeof value === "string" && value.length > 0 ? value : void 0;
@@ -4989,6 +5184,11 @@ function walkSubtree(root, detectLists, startInModal = false) {
4989
5184
  const isSecure = !!props.secureTextEntry || isAppilotsSensitiveMarked(props);
4990
5185
  const rawValue = typeof props.value === "string" ? props.value : void 0;
4991
5186
  const safeValue = isSecure ? rawValue && rawValue.length > 0 ? "<hidden>" : void 0 : rawValue;
5187
+ const filled = typeof rawValue === "string" ? rawValue.length > 0 : void 0;
5188
+ const wrapper = readWrapperProps(fiber);
5189
+ const required = readRequiredProp(props) ?? (wrapper ? readRequiredProp(wrapper) : void 0);
5190
+ const invalid = readInvalidProp(props) ?? (wrapper ? readInvalidProp(wrapper) : void 0);
5191
+ const focused = isFiberFocused(fiber);
4992
5192
  const identity = resolveIdentity(props, inferLabel(props));
4993
5193
  const geom = readGeometry(fiber);
4994
5194
  result.inputs.push(
@@ -5002,6 +5202,13 @@ function walkSubtree(root, detectLists, startInModal = false) {
5002
5202
  editable: props.editable !== false,
5003
5203
  secure: isSecure,
5004
5204
  type: inferInputType(props),
5205
+ // Each one omitted rather than sent as `false` when the app
5206
+ // says nothing: absent is UNKNOWN on all four, and the wire
5207
+ // has to keep that apart from a declared negative.
5208
+ ...required !== void 0 ? { required } : {},
5209
+ ...invalid !== void 0 ? { invalid } : {},
5210
+ ...filled !== void 0 ? { filled } : {},
5211
+ ...focused !== void 0 ? { focused } : {},
5005
5212
  // A field whose return key submits (#398). On a search box or
5006
5213
  // a one-field login this IS the submit, and without the flag
5007
5214
  // the screen looks like a form with no way to send it.
@@ -5476,9 +5683,21 @@ function routesBelongToSameFeature(a, b) {
5476
5683
  // src/navigation/navigationState.ts
5477
5684
  var _currentScreen = null;
5478
5685
  var _navigationRef = null;
5686
+ var MAX_VISITED_SCREENS = 8;
5687
+ var _visited = [];
5688
+ function recordScreenVisit(screen) {
5689
+ if (!screen) return;
5690
+ if (_visited[_visited.length - 1] === screen) return;
5691
+ _visited.push(screen);
5692
+ if (_visited.length > MAX_VISITED_SCREENS) _visited.shift();
5693
+ }
5694
+ function getVisitedScreens() {
5695
+ return [..._visited];
5696
+ }
5479
5697
  function setCurrentScreen(screen) {
5480
5698
  appilotsDebugLog(`setCurrentScreen: "${_currentScreen}" \u2192 "${screen}"`);
5481
5699
  _currentScreen = screen;
5700
+ recordScreenVisit(screen);
5482
5701
  }
5483
5702
  function getCurrentScreen() {
5484
5703
  const activePath = getActiveRouteNames();
@@ -5492,6 +5711,7 @@ function getCurrentScreen() {
5492
5711
  }
5493
5712
  _currentScreen = deepest;
5494
5713
  }
5714
+ recordScreenVisit(deepest);
5495
5715
  if (deepest) return deepest;
5496
5716
  }
5497
5717
  if (_navigationRef) {
@@ -5503,6 +5723,7 @@ function getCurrentScreen() {
5503
5723
  );
5504
5724
  _currentScreen = liveScreen;
5505
5725
  }
5726
+ recordScreenVisit(liveScreen);
5506
5727
  return liveScreen;
5507
5728
  }
5508
5729
  }
@@ -5562,6 +5783,13 @@ function collectRouteNamesFromState(state, out = /* @__PURE__ */ new Set()) {
5562
5783
  }
5563
5784
  return out;
5564
5785
  }
5786
+ function backStackOf(state) {
5787
+ const deepest = getDeepestNavigatorState(state);
5788
+ const routes = deepest?.routes;
5789
+ if (!Array.isArray(routes)) return [];
5790
+ const index = typeof deepest.index === "number" ? deepest.index : routes.length - 1;
5791
+ return routes.slice(0, index + 1).map((route) => route?.name).filter((name) => typeof name === "string" && name.length > 0);
5792
+ }
5565
5793
  function getDeepestNavigatorState(state) {
5566
5794
  if (!state) return void 0;
5567
5795
  const route = state.routes?.[state.index ?? 0];
@@ -5576,7 +5804,8 @@ function getNavigationStateSnapshot() {
5576
5804
  activePath: current,
5577
5805
  rootRouteNames: current,
5578
5806
  currentRouteNames: current,
5579
- routeNames: current
5807
+ routeNames: current,
5808
+ ...getVisitedScreens().length > 0 ? { visited: getVisitedScreens() } : {}
5580
5809
  } : null;
5581
5810
  }
5582
5811
  try {
@@ -5589,20 +5818,27 @@ function getNavigationStateSnapshot() {
5589
5818
  rootRouteNames: current,
5590
5819
  currentRouteNames: current,
5591
5820
  routeNames: current,
5592
- canGoBack: typeof _navigationRef.canGoBack === "function" ? _navigationRef.canGoBack() : void 0
5821
+ canGoBack: typeof _navigationRef.canGoBack === "function" ? _navigationRef.canGoBack() : void 0,
5822
+ ...getVisitedScreens().length > 0 ? { visited: getVisitedScreens() } : {}
5593
5823
  } : null;
5594
5824
  }
5595
5825
  const activePath = getActiveRouteNamesFromState(state);
5596
5826
  const currentRouteName = activePath[activePath.length - 1] ?? _currentScreen ?? void 0;
5597
5827
  const deepestState = getDeepestNavigatorState(state);
5598
5828
  const routeNames = Array.from(collectRouteNamesFromState(state));
5829
+ const backStack = backStackOf(state);
5599
5830
  return {
5600
5831
  currentRouteName,
5601
5832
  activePath,
5602
5833
  rootRouteNames: routeNamesAtState(state),
5603
5834
  currentRouteNames: routeNamesAtState(deepestState),
5604
5835
  routeNames,
5605
- canGoBack: typeof _navigationRef.canGoBack === "function" ? _navigationRef.canGoBack() : void 0
5836
+ canGoBack: typeof _navigationRef.canGoBack === "function" ? _navigationRef.canGoBack() : void 0,
5837
+ // Both omitted when empty rather than sent as `[]`: an empty array
5838
+ // reads as "we looked and there is nowhere to go back to", and on
5839
+ // an app with no reachable navigator we did not look.
5840
+ ...backStack.length > 0 ? { backStack } : {},
5841
+ ...getVisitedScreens().length > 0 ? { visited: getVisitedScreens() } : {}
5606
5842
  };
5607
5843
  } catch {
5608
5844
  const current = _currentScreen ? [_currentScreen] : [];
@@ -5611,7 +5847,8 @@ function getNavigationStateSnapshot() {
5611
5847
  activePath: current,
5612
5848
  rootRouteNames: current,
5613
5849
  currentRouteNames: current,
5614
- routeNames: current
5850
+ routeNames: current,
5851
+ ...getVisitedScreens().length > 0 ? { visited: getVisitedScreens() } : {}
5615
5852
  } : null;
5616
5853
  }
5617
5854
  }
@@ -5765,13 +6002,37 @@ async function waitForLoadingSettle(opts = {}) {
5765
6002
  const stableTarget = opts.stableReads ?? 2;
5766
6003
  const pressedComponentId = opts.pressedComponentId ?? null;
5767
6004
  const fromScreen = opts.fromScreen ?? null;
6005
+ const busyMaxMs = Math.max(maxMs, opts.busyMaxMs ?? 0);
5768
6006
  const startMs = Date.now();
5769
- const remaining = () => maxMs - (Date.now() - startMs);
5770
6007
  let probe = probeLoadingState(pressedComponentId, getCurrentScreen());
5771
6008
  let probeCount = 1;
5772
6009
  let stableCount = 1;
5773
6010
  let lastFingerprint = probe.fingerprint;
5774
6011
  let interval = initialPollMs;
6012
+ let sawBusy = false;
6013
+ let busySpent = false;
6014
+ let extendedPastBudget = false;
6015
+ let graceDeadlineMs;
6016
+ const noteEvidence = () => {
6017
+ const elapsedMs = Date.now() - startMs;
6018
+ if (probe.loading) {
6019
+ if (busySpent) return;
6020
+ sawBusy = true;
6021
+ if (elapsedMs > maxMs) extendedPastBudget = true;
6022
+ return;
6023
+ }
6024
+ if (!sawBusy || busySpent) return;
6025
+ if (extendedPastBudget) {
6026
+ graceDeadlineMs = Math.min(busyMaxMs, elapsedMs + (stableTarget + 1) * pollMaxMs);
6027
+ }
6028
+ busySpent = true;
6029
+ };
6030
+ const remaining = () => {
6031
+ const elapsedMs = Date.now() - startMs;
6032
+ if (probe.loading && !busySpent) return busyMaxMs - elapsedMs;
6033
+ return Math.max(maxMs, graceDeadlineMs ?? 0) - elapsedMs;
6034
+ };
6035
+ noteEvidence();
5775
6036
  const looksImmediatelyClean = !probe.loading && !probe.pressedDisabled && (!pressedComponentId || probe.pressedFound);
5776
6037
  if (looksImmediatelyClean) {
5777
6038
  await sleepMs(Math.min(80, Math.max(0, remaining())));
@@ -5788,6 +6049,7 @@ async function waitForLoadingSettle(opts = {}) {
5788
6049
  };
5789
6050
  }
5790
6051
  probe = recheck;
6052
+ noteEvidence();
5791
6053
  lastFingerprint = recheck.fingerprint;
5792
6054
  stableCount = 1;
5793
6055
  }
@@ -5808,6 +6070,7 @@ async function waitForLoadingSettle(opts = {}) {
5808
6070
  }
5809
6071
  probe = probeLoadingState(pressedComponentId, currentScreen);
5810
6072
  probeCount++;
6073
+ noteEvidence();
5811
6074
  const quiescent = !probe.loading && !probe.pressedDisabled && probe.fingerprint === lastFingerprint;
5812
6075
  if (quiescent) {
5813
6076
  stableCount++;
@@ -5839,7 +6102,7 @@ async function waitForLoadingSettle(opts = {}) {
5839
6102
  }
5840
6103
 
5841
6104
  // src/version.ts
5842
- var SDK_VERSION2 = "0.11.1";
6105
+ var SDK_VERSION2 = "0.11.3";
5843
6106
  var FiberSentinel = class extends React2__default.default.Component {
5844
6107
  componentDidMount() {
5845
6108
  const fiber = this._reactInternals ?? this._reactInternalFiber ?? null;
@@ -5983,6 +6246,21 @@ function probeNameMangling(components) {
5983
6246
  return _lastProbe;
5984
6247
  }
5985
6248
 
6249
+ // src/platform/textInputFocus.ts
6250
+ function createTextInputFocusHost(textInput) {
6251
+ const read = textInput?.State?.currentlyFocusedInput;
6252
+ if (typeof read !== "function") return null;
6253
+ return {
6254
+ focusedInstance() {
6255
+ try {
6256
+ return textInput.State.currentlyFocusedInput() ?? null;
6257
+ } catch {
6258
+ return null;
6259
+ }
6260
+ }
6261
+ };
6262
+ }
6263
+
5986
6264
  // src/platform/fabricGeometry.ts
5987
6265
  function readRect(instance) {
5988
6266
  const candidate = instance;
@@ -6113,6 +6391,10 @@ function AppilotsProviderInner({
6113
6391
  setGeometryHost(createFabricGeometryHost(reactNative.Dimensions));
6114
6392
  return () => setGeometryHost(null);
6115
6393
  }, []);
6394
+ React2.useEffect(() => {
6395
+ setFocusHost(createTextInputFocusHost(reactNative.TextInput));
6396
+ return () => setFocusHost(null);
6397
+ }, []);
6116
6398
  const trackNativeDialogs = config.trackNativeDialogs !== false;
6117
6399
  React2.useEffect(() => {
6118
6400
  if (!trackNativeDialogs) return;
@@ -6236,6 +6518,7 @@ exports.componentRegistry = componentRegistry;
6236
6518
  exports.createComponentRegistry = createComponentRegistry;
6237
6519
  exports.createElementRegistry = createElementRegistry;
6238
6520
  exports.createListRegistry = createListRegistry;
6521
+ exports.createSnapshotDeltaTracker = createSnapshotDeltaTracker;
6239
6522
  exports.defaultDarkTheme = defaultDarkTheme;
6240
6523
  exports.defaultLightTheme = defaultLightTheme;
6241
6524
  exports.deriveInteractionElements = deriveInteractionElements;