@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.
@@ -155,7 +155,7 @@ new RegExp(
155
155
  );
156
156
  var CREATE_EDIT_ROUTE_RE = new RegExp(`(${build(CREATE_EDIT_ROUTE)})`, "i");
157
157
 
158
- // ../shared/dist/chunk-43YQ52Q7.mjs
158
+ // ../shared/dist/chunk-SWQOZWHP.mjs
159
159
  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;
160
160
  function isSensitiveFieldName(id, label, placeholder, type) {
161
161
  if (type === "password") return true;
@@ -1364,6 +1364,7 @@ function snapshotHasVisibleSubmitButton(snap) {
1364
1364
  );
1365
1365
  }
1366
1366
  function snapshotHasVisibleValidation(snap) {
1367
+ if ((snap.inputs ?? []).some((input) => input.invalid === true)) return true;
1367
1368
  return (snap.texts ?? []).some(
1368
1369
  (text) => typeof text === "string" && FORM_VALIDATION_TEXT_RE.test(text)
1369
1370
  );
@@ -1509,6 +1510,8 @@ var ChatSessionMachine = class {
1509
1510
  /** Tear down timers and the escalation stream. Safe to call twice. */
1510
1511
  dispose() {
1511
1512
  this.disposed = true;
1513
+ this.abortController?.abort();
1514
+ this.currentTurn = null;
1512
1515
  this.closeEscalationStream();
1513
1516
  }
1514
1517
  setState(partial) {
@@ -1555,7 +1558,7 @@ var ChatSessionMachine = class {
1555
1558
  }
1556
1559
  // ── Escalation: request, stream, and message routing ─────────
1557
1560
  async requestHuman(reason = "user_requested") {
1558
- if (this.state.escalation) return;
1561
+ if (this.disposed || this.state.escalation) return;
1559
1562
  try {
1560
1563
  const lastError = this.state.messages.slice().reverse().find((m) => m.role === "assistant" && m.id.startsWith("error_"));
1561
1564
  const result = await this.deps.client.requestEscalation(reason, lastError?.content);
@@ -1729,7 +1732,7 @@ var ChatSessionMachine = class {
1729
1732
  // ── Continuation: when all current-turn actions finish, ask the
1730
1733
  // model what to do next, given the outcomes + fresh snapshot.
1731
1734
  async runContinuation(turn) {
1732
- if (this.continuationInFlight) return;
1735
+ if (this.disposed || this.continuationInFlight) return;
1733
1736
  this.continuationInFlight = true;
1734
1737
  try {
1735
1738
  this.setState({ isLoading: true, loadingStatusKey: "statusWaitingApp" });
@@ -1752,6 +1755,7 @@ var ChatSessionMachine = class {
1752
1755
  pressedComponentId: pressedId,
1753
1756
  navigateTargetScreen
1754
1757
  });
1758
+ if (this.disposed || this.currentTurn !== turn) return;
1755
1759
  const loadingPending = settle.loadingPending;
1756
1760
  for (const id of settle.toolWithoutEffectIds) toolWithoutEffectIds.add(id);
1757
1761
  const context = this.deps.adapter.buildContext({ loadingPending });
@@ -1817,6 +1821,7 @@ var ChatSessionMachine = class {
1817
1821
  `ChatSessionMachine: running continuation hop=${turn.hop} (${results.filter((r) => r.success).length}/${results.length} succeeded` + (exhausted.length > 0 ? `, ${exhausted.length} recovery-exhausted` : "") + ")"
1818
1822
  );
1819
1823
  const response = await this.deps.client.continueAgent(results, context, turn.hop);
1824
+ if (this.disposed) return;
1820
1825
  this.addAssistantMessage(response.message);
1821
1826
  this.deps.emit({
1822
1827
  type: "agent:message",
@@ -1904,7 +1909,7 @@ var ChatSessionMachine = class {
1904
1909
  }
1905
1910
  // ── Send / cancel / clear ────────────────────────────────────
1906
1911
  async sendMessage(content) {
1907
- if (!content.trim() || this.state.isLoading) return;
1912
+ if (this.disposed || !content.trim() || this.state.isLoading) return;
1908
1913
  if (this.state.escalation) {
1909
1914
  await this.sendEscalationMessage(content.trim());
1910
1915
  return;
@@ -1931,7 +1936,7 @@ var ChatSessionMachine = class {
1931
1936
  const abortController = new AbortController();
1932
1937
  this.abortController = abortController;
1933
1938
  try {
1934
- const context = this.deps.adapter.buildContext();
1939
+ const context = this.deps.adapter.buildContext({ newMission: true });
1935
1940
  appilotsDebugLog(
1936
1941
  `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}}`
1937
1942
  );
@@ -2132,6 +2137,7 @@ var ActionQueueMachine = class {
2132
2137
  processed = /* @__PURE__ */ new Set();
2133
2138
  /** Guards against overlapping sequential runs. */
2134
2139
  draining = false;
2140
+ disposed = false;
2135
2141
  /** Set when new work arrives while a drain is in flight. */
2136
2142
  drainRequested = false;
2137
2143
  /** Set while a drain is queued on the microtask queue. */
@@ -2146,6 +2152,12 @@ var ActionQueueMachine = class {
2146
2152
  this.rejectAction = this.rejectAction.bind(this);
2147
2153
  }
2148
2154
  // ── State plumbing ───────────────────────────────────────────
2155
+ dispose() {
2156
+ this.disposed = true;
2157
+ this.drainRequested = false;
2158
+ this.setActions([]);
2159
+ this.listeners.clear();
2160
+ }
2149
2161
  getState() {
2150
2162
  return this.state;
2151
2163
  }
@@ -2171,7 +2183,7 @@ var ActionQueueMachine = class {
2171
2183
  }
2172
2184
  // ── Event intake: new actions from the agent ─────────────────
2173
2185
  handleEvent(event) {
2174
- if (event.type !== "agent:action:start") return;
2186
+ if (this.disposed || event.type !== "agent:action:start") return;
2175
2187
  const action = event.data?.action;
2176
2188
  if (!action?.id) return;
2177
2189
  appilotsDebugLog(
@@ -2198,7 +2210,7 @@ var ActionQueueMachine = class {
2198
2210
  * batching before its effect ran.)
2199
2211
  */
2200
2212
  scheduleDrain() {
2201
- if (!this.options.autoExecute) return;
2213
+ if (this.disposed || !this.options.autoExecute) return;
2202
2214
  if (this.draining) {
2203
2215
  this.drainRequested = true;
2204
2216
  return;
@@ -2207,7 +2219,7 @@ var ActionQueueMachine = class {
2207
2219
  this.drainScheduled = true;
2208
2220
  queueMicrotask(() => {
2209
2221
  this.drainScheduled = false;
2210
- if (!this.options.autoExecute) return;
2222
+ if (this.disposed || !this.options.autoExecute) return;
2211
2223
  if (this.draining) {
2212
2224
  this.drainRequested = true;
2213
2225
  return;
@@ -2330,6 +2342,7 @@ var ActionQueueMachine = class {
2330
2342
  let preNavScreen = null;
2331
2343
  let preNavSignature = null;
2332
2344
  for (const [index, action] of pending.entries()) {
2345
+ if (this.disposed) return;
2333
2346
  if (action.type === "confirm") {
2334
2347
  continue;
2335
2348
  }
@@ -2449,6 +2462,7 @@ var ActionQueueMachine = class {
2449
2462
  }
2450
2463
  // ── Approve & execute an action ─────────────────────────────
2451
2464
  async approveAction(actionId) {
2465
+ if (this.disposed) return false;
2452
2466
  const action = this.state.actions.find((a) => a.id === actionId);
2453
2467
  if (!action) return false;
2454
2468
  appilotsDebugLog(
@@ -3264,6 +3278,91 @@ function clampSnapshotToWireLimits(snapshot) {
3264
3278
  if (cut.dropped) snapshot.truncated = true;
3265
3279
  return snapshot;
3266
3280
  }
3281
+ var MAX_DELTA_FIELD_IDS = 12;
3282
+ function isFilled(input) {
3283
+ if (typeof input.filled === "boolean") return input.filled;
3284
+ return typeof input.value === "string" && input.value.length > 0;
3285
+ }
3286
+ function fingerprintSnapshot(snap) {
3287
+ const texts = /* @__PURE__ */ new Set();
3288
+ for (const text of snap.texts ?? []) {
3289
+ if (typeof text !== "string") continue;
3290
+ const trimmed = text.trim();
3291
+ if (trimmed) texts.add(trimmed);
3292
+ }
3293
+ let visibleRows = 0;
3294
+ let totalRows = 0;
3295
+ for (const list of snap.lists ?? []) {
3296
+ visibleRows += list.items?.length ?? 0;
3297
+ if (typeof list.itemCount === "number") totalRows += list.itemCount;
3298
+ }
3299
+ const filledDeclaredFields = /* @__PURE__ */ new Set();
3300
+ let anyInvalid = false;
3301
+ for (const input of snap.inputs ?? []) {
3302
+ if (input.invalid === true) anyInvalid = true;
3303
+ if (input.provenance !== "declared") continue;
3304
+ if (!input.id) continue;
3305
+ if (isFilled(input)) filledDeclaredFields.add(input.id);
3306
+ }
3307
+ return {
3308
+ route: snap.route ?? null,
3309
+ modal: snap.modalOpen === true || !!snap.nativeDialog,
3310
+ loading: snap.loading === true,
3311
+ texts,
3312
+ visibleRows,
3313
+ totalRows,
3314
+ filledDeclaredFields,
3315
+ anyInvalid
3316
+ };
3317
+ }
3318
+ function countMissing(from, inSet) {
3319
+ let n = 0;
3320
+ for (const value of from) if (!inSet.has(value)) n += 1;
3321
+ return n;
3322
+ }
3323
+ function namesMissing(from, inSet) {
3324
+ const out = [];
3325
+ for (const value of from) if (!inSet.has(value)) out.push(value);
3326
+ out.sort();
3327
+ return out.slice(0, MAX_DELTA_FIELD_IDS);
3328
+ }
3329
+ function diffFingerprints(prev, next) {
3330
+ const delta = {};
3331
+ if (prev.route !== next.route) delta.routeChanged = true;
3332
+ if (!prev.modal && next.modal) delta.modalOpened = true;
3333
+ if (prev.modal && !next.modal) delta.modalClosed = true;
3334
+ if (!prev.loading && next.loading) delta.loadingStarted = true;
3335
+ if (prev.loading && !next.loading) delta.loadingFinished = true;
3336
+ const added = countMissing(next.texts, prev.texts);
3337
+ const removed = countMissing(prev.texts, next.texts);
3338
+ if (added > 0) delta.textsAdded = added;
3339
+ if (removed > 0) delta.textsRemoved = removed;
3340
+ const visibleRowsDelta = next.visibleRows - prev.visibleRows;
3341
+ if (visibleRowsDelta !== 0) delta.visibleRowsDelta = visibleRowsDelta;
3342
+ const totalRowsDelta = next.totalRows - prev.totalRows;
3343
+ if (totalRowsDelta !== 0) delta.totalRowsDelta = totalRowsDelta;
3344
+ const newlyFilled = namesMissing(next.filledDeclaredFields, prev.filledDeclaredFields);
3345
+ if (newlyFilled.length > 0) delta.fieldsNewlyFilled = newlyFilled;
3346
+ const cleared = namesMissing(prev.filledDeclaredFields, next.filledDeclaredFields);
3347
+ if (cleared.length > 0) delta.fieldsCleared = cleared;
3348
+ if (!prev.anyInvalid && next.anyInvalid) delta.invalidAppeared = true;
3349
+ if (Object.keys(delta).length === 0) delta.unchanged = true;
3350
+ return delta;
3351
+ }
3352
+ function createSnapshotDeltaTracker() {
3353
+ let previous;
3354
+ return {
3355
+ observe(snap) {
3356
+ const next = fingerprintSnapshot(snap);
3357
+ const delta = previous ? diffFingerprints(previous, next) : void 0;
3358
+ previous = next;
3359
+ return delta;
3360
+ },
3361
+ reset() {
3362
+ previous = void 0;
3363
+ }
3364
+ };
3365
+ }
3267
3366
 
3268
3367
  // src/registry/ComponentRegistry.ts
3269
3368
  var ComponentRegistryImpl = class {
@@ -4465,6 +4564,37 @@ function rectForNode(node) {
4465
4564
  return _rectByNode.get(node);
4466
4565
  }
4467
4566
 
4567
+ // src/introspection/focusState.ts
4568
+ var _host2 = null;
4569
+ function setFocusHost(host) {
4570
+ _host2 = host;
4571
+ }
4572
+ var HOST_SEARCH_DEPTH2 = 6;
4573
+ function isFiberFocused(fiber) {
4574
+ const host = _host2;
4575
+ if (!host || !fiber) return void 0;
4576
+ let focused = null;
4577
+ try {
4578
+ focused = host.focusedInstance();
4579
+ } catch {
4580
+ return void 0;
4581
+ }
4582
+ if (!focused) return false;
4583
+ const stack = [{ node: fiber, depth: 0 }];
4584
+ while (stack.length > 0) {
4585
+ const { node, depth } = stack.pop();
4586
+ const current = node;
4587
+ if (!current) continue;
4588
+ if (current.stateNode && current.stateNode === focused) return true;
4589
+ if (depth < HOST_SEARCH_DEPTH2 && current.child) {
4590
+ stack.push({ node: current.child, depth: depth + 1 });
4591
+ const sibling = current.child.sibling;
4592
+ if (sibling) stack.push({ node: sibling, depth: depth + 1 });
4593
+ }
4594
+ }
4595
+ return false;
4596
+ }
4597
+
4468
4598
  // src/introspection/walkFiber.ts
4469
4599
  function getTypeName(type) {
4470
4600
  if (!type) return null;
@@ -4533,6 +4663,71 @@ function isAppilotsSensitiveMarked(props) {
4533
4663
  if (!props) return false;
4534
4664
  return props.__appilotsSensitive === true || props["data-appilots-sensitive"] === true || props.appilotsSensitive === true;
4535
4665
  }
4666
+ function booleanish(value) {
4667
+ if (typeof value === "boolean") return value;
4668
+ if (typeof value === "string") {
4669
+ const normalized = value.trim().toLowerCase();
4670
+ if (normalized === "true") return true;
4671
+ if (normalized === "false") return false;
4672
+ }
4673
+ return void 0;
4674
+ }
4675
+ function firstBooleanish(props, keys) {
4676
+ for (const key of keys) {
4677
+ const resolved = booleanish(props?.[key]);
4678
+ if (resolved !== void 0) return resolved;
4679
+ }
4680
+ return void 0;
4681
+ }
4682
+ function readRequiredProp(props) {
4683
+ const direct = firstBooleanish(props, [
4684
+ "required",
4685
+ "aria-required",
4686
+ "isRequired",
4687
+ "accessibilityRequired"
4688
+ ]);
4689
+ if (direct !== void 0) return direct;
4690
+ return booleanish(props?.accessibilityState?.required);
4691
+ }
4692
+ function readInvalidProp(props) {
4693
+ const direct = firstBooleanish(props, ["invalid", "aria-invalid", "isInvalid", "hasError"]);
4694
+ if (direct !== void 0) return direct;
4695
+ const state = booleanish(props?.accessibilityState?.invalid);
4696
+ if (state !== void 0) return state;
4697
+ for (const key of ["error", "errorMessage", "errorText"]) {
4698
+ const value = props?.[key];
4699
+ const asBoolean = booleanish(value);
4700
+ if (asBoolean !== void 0) return asBoolean;
4701
+ if (typeof value === "string") return value.trim().length > 0;
4702
+ }
4703
+ return void 0;
4704
+ }
4705
+ var WRAPPER_PROP_SEARCH_DEPTH = 3;
4706
+ var WRAPPER_SUBTREE_BUDGET = 64;
4707
+ function wrapsExactlyOneInput(fiber) {
4708
+ let seen = 0;
4709
+ let visited = 0;
4710
+ const stack = [fiber?.child];
4711
+ while (stack.length > 0) {
4712
+ if ((visited += 1) > WRAPPER_SUBTREE_BUDGET) return false;
4713
+ const node = stack.pop();
4714
+ if (!node) continue;
4715
+ if (matchesName(node, "TextInput") && (seen += 1) > 1) return false;
4716
+ if (node.child) stack.push(node.child);
4717
+ if (node.sibling) stack.push(node.sibling);
4718
+ }
4719
+ return seen === 1;
4720
+ }
4721
+ function readWrapperProps(fiber) {
4722
+ let node = fiber?.return;
4723
+ for (let depth = 0; node && depth < WRAPPER_PROP_SEARCH_DEPTH; depth += 1, node = node.return) {
4724
+ const props = node.memoizedProps;
4725
+ if (!props) continue;
4726
+ if (readInvalidProp(props) === void 0 && readRequiredProp(props) === void 0) continue;
4727
+ return wrapsExactlyOneInput(node) ? props : void 0;
4728
+ }
4729
+ return void 0;
4730
+ }
4536
4731
  function stringProp(props, name) {
4537
4732
  const value = props?.[name];
4538
4733
  return typeof value === "string" && value.length > 0 ? value : void 0;
@@ -4983,6 +5178,11 @@ function walkSubtree(root, detectLists, startInModal = false) {
4983
5178
  const isSecure = !!props.secureTextEntry || isAppilotsSensitiveMarked(props);
4984
5179
  const rawValue = typeof props.value === "string" ? props.value : void 0;
4985
5180
  const safeValue = isSecure ? rawValue && rawValue.length > 0 ? "<hidden>" : void 0 : rawValue;
5181
+ const filled = typeof rawValue === "string" ? rawValue.length > 0 : void 0;
5182
+ const wrapper = readWrapperProps(fiber);
5183
+ const required = readRequiredProp(props) ?? (wrapper ? readRequiredProp(wrapper) : void 0);
5184
+ const invalid = readInvalidProp(props) ?? (wrapper ? readInvalidProp(wrapper) : void 0);
5185
+ const focused = isFiberFocused(fiber);
4986
5186
  const identity = resolveIdentity(props, inferLabel(props));
4987
5187
  const geom = readGeometry(fiber);
4988
5188
  result.inputs.push(
@@ -4996,6 +5196,13 @@ function walkSubtree(root, detectLists, startInModal = false) {
4996
5196
  editable: props.editable !== false,
4997
5197
  secure: isSecure,
4998
5198
  type: inferInputType(props),
5199
+ // Each one omitted rather than sent as `false` when the app
5200
+ // says nothing: absent is UNKNOWN on all four, and the wire
5201
+ // has to keep that apart from a declared negative.
5202
+ ...required !== void 0 ? { required } : {},
5203
+ ...invalid !== void 0 ? { invalid } : {},
5204
+ ...filled !== void 0 ? { filled } : {},
5205
+ ...focused !== void 0 ? { focused } : {},
4999
5206
  // A field whose return key submits (#398). On a search box or
5000
5207
  // a one-field login this IS the submit, and without the flag
5001
5208
  // the screen looks like a form with no way to send it.
@@ -5470,9 +5677,21 @@ function routesBelongToSameFeature(a, b) {
5470
5677
  // src/navigation/navigationState.ts
5471
5678
  var _currentScreen = null;
5472
5679
  var _navigationRef = null;
5680
+ var MAX_VISITED_SCREENS = 8;
5681
+ var _visited = [];
5682
+ function recordScreenVisit(screen) {
5683
+ if (!screen) return;
5684
+ if (_visited[_visited.length - 1] === screen) return;
5685
+ _visited.push(screen);
5686
+ if (_visited.length > MAX_VISITED_SCREENS) _visited.shift();
5687
+ }
5688
+ function getVisitedScreens() {
5689
+ return [..._visited];
5690
+ }
5473
5691
  function setCurrentScreen(screen) {
5474
5692
  appilotsDebugLog(`setCurrentScreen: "${_currentScreen}" \u2192 "${screen}"`);
5475
5693
  _currentScreen = screen;
5694
+ recordScreenVisit(screen);
5476
5695
  }
5477
5696
  function getCurrentScreen() {
5478
5697
  const activePath = getActiveRouteNames();
@@ -5486,6 +5705,7 @@ function getCurrentScreen() {
5486
5705
  }
5487
5706
  _currentScreen = deepest;
5488
5707
  }
5708
+ recordScreenVisit(deepest);
5489
5709
  if (deepest) return deepest;
5490
5710
  }
5491
5711
  if (_navigationRef) {
@@ -5497,6 +5717,7 @@ function getCurrentScreen() {
5497
5717
  );
5498
5718
  _currentScreen = liveScreen;
5499
5719
  }
5720
+ recordScreenVisit(liveScreen);
5500
5721
  return liveScreen;
5501
5722
  }
5502
5723
  }
@@ -5556,6 +5777,13 @@ function collectRouteNamesFromState(state, out = /* @__PURE__ */ new Set()) {
5556
5777
  }
5557
5778
  return out;
5558
5779
  }
5780
+ function backStackOf(state) {
5781
+ const deepest = getDeepestNavigatorState(state);
5782
+ const routes = deepest?.routes;
5783
+ if (!Array.isArray(routes)) return [];
5784
+ const index = typeof deepest.index === "number" ? deepest.index : routes.length - 1;
5785
+ return routes.slice(0, index + 1).map((route) => route?.name).filter((name) => typeof name === "string" && name.length > 0);
5786
+ }
5559
5787
  function getDeepestNavigatorState(state) {
5560
5788
  if (!state) return void 0;
5561
5789
  const route = state.routes?.[state.index ?? 0];
@@ -5570,7 +5798,8 @@ function getNavigationStateSnapshot() {
5570
5798
  activePath: current,
5571
5799
  rootRouteNames: current,
5572
5800
  currentRouteNames: current,
5573
- routeNames: current
5801
+ routeNames: current,
5802
+ ...getVisitedScreens().length > 0 ? { visited: getVisitedScreens() } : {}
5574
5803
  } : null;
5575
5804
  }
5576
5805
  try {
@@ -5583,20 +5812,27 @@ function getNavigationStateSnapshot() {
5583
5812
  rootRouteNames: current,
5584
5813
  currentRouteNames: current,
5585
5814
  routeNames: current,
5586
- canGoBack: typeof _navigationRef.canGoBack === "function" ? _navigationRef.canGoBack() : void 0
5815
+ canGoBack: typeof _navigationRef.canGoBack === "function" ? _navigationRef.canGoBack() : void 0,
5816
+ ...getVisitedScreens().length > 0 ? { visited: getVisitedScreens() } : {}
5587
5817
  } : null;
5588
5818
  }
5589
5819
  const activePath = getActiveRouteNamesFromState(state);
5590
5820
  const currentRouteName = activePath[activePath.length - 1] ?? _currentScreen ?? void 0;
5591
5821
  const deepestState = getDeepestNavigatorState(state);
5592
5822
  const routeNames = Array.from(collectRouteNamesFromState(state));
5823
+ const backStack = backStackOf(state);
5593
5824
  return {
5594
5825
  currentRouteName,
5595
5826
  activePath,
5596
5827
  rootRouteNames: routeNamesAtState(state),
5597
5828
  currentRouteNames: routeNamesAtState(deepestState),
5598
5829
  routeNames,
5599
- canGoBack: typeof _navigationRef.canGoBack === "function" ? _navigationRef.canGoBack() : void 0
5830
+ canGoBack: typeof _navigationRef.canGoBack === "function" ? _navigationRef.canGoBack() : void 0,
5831
+ // Both omitted when empty rather than sent as `[]`: an empty array
5832
+ // reads as "we looked and there is nowhere to go back to", and on
5833
+ // an app with no reachable navigator we did not look.
5834
+ ...backStack.length > 0 ? { backStack } : {},
5835
+ ...getVisitedScreens().length > 0 ? { visited: getVisitedScreens() } : {}
5600
5836
  };
5601
5837
  } catch {
5602
5838
  const current = _currentScreen ? [_currentScreen] : [];
@@ -5605,7 +5841,8 @@ function getNavigationStateSnapshot() {
5605
5841
  activePath: current,
5606
5842
  rootRouteNames: current,
5607
5843
  currentRouteNames: current,
5608
- routeNames: current
5844
+ routeNames: current,
5845
+ ...getVisitedScreens().length > 0 ? { visited: getVisitedScreens() } : {}
5609
5846
  } : null;
5610
5847
  }
5611
5848
  }
@@ -5759,13 +5996,37 @@ async function waitForLoadingSettle(opts = {}) {
5759
5996
  const stableTarget = opts.stableReads ?? 2;
5760
5997
  const pressedComponentId = opts.pressedComponentId ?? null;
5761
5998
  const fromScreen = opts.fromScreen ?? null;
5999
+ const busyMaxMs = Math.max(maxMs, opts.busyMaxMs ?? 0);
5762
6000
  const startMs = Date.now();
5763
- const remaining = () => maxMs - (Date.now() - startMs);
5764
6001
  let probe = probeLoadingState(pressedComponentId, getCurrentScreen());
5765
6002
  let probeCount = 1;
5766
6003
  let stableCount = 1;
5767
6004
  let lastFingerprint = probe.fingerprint;
5768
6005
  let interval = initialPollMs;
6006
+ let sawBusy = false;
6007
+ let busySpent = false;
6008
+ let extendedPastBudget = false;
6009
+ let graceDeadlineMs;
6010
+ const noteEvidence = () => {
6011
+ const elapsedMs = Date.now() - startMs;
6012
+ if (probe.loading) {
6013
+ if (busySpent) return;
6014
+ sawBusy = true;
6015
+ if (elapsedMs > maxMs) extendedPastBudget = true;
6016
+ return;
6017
+ }
6018
+ if (!sawBusy || busySpent) return;
6019
+ if (extendedPastBudget) {
6020
+ graceDeadlineMs = Math.min(busyMaxMs, elapsedMs + (stableTarget + 1) * pollMaxMs);
6021
+ }
6022
+ busySpent = true;
6023
+ };
6024
+ const remaining = () => {
6025
+ const elapsedMs = Date.now() - startMs;
6026
+ if (probe.loading && !busySpent) return busyMaxMs - elapsedMs;
6027
+ return Math.max(maxMs, graceDeadlineMs ?? 0) - elapsedMs;
6028
+ };
6029
+ noteEvidence();
5769
6030
  const looksImmediatelyClean = !probe.loading && !probe.pressedDisabled && (!pressedComponentId || probe.pressedFound);
5770
6031
  if (looksImmediatelyClean) {
5771
6032
  await sleepMs(Math.min(80, Math.max(0, remaining())));
@@ -5782,6 +6043,7 @@ async function waitForLoadingSettle(opts = {}) {
5782
6043
  };
5783
6044
  }
5784
6045
  probe = recheck;
6046
+ noteEvidence();
5785
6047
  lastFingerprint = recheck.fingerprint;
5786
6048
  stableCount = 1;
5787
6049
  }
@@ -5802,6 +6064,7 @@ async function waitForLoadingSettle(opts = {}) {
5802
6064
  }
5803
6065
  probe = probeLoadingState(pressedComponentId, currentScreen);
5804
6066
  probeCount++;
6067
+ noteEvidence();
5805
6068
  const quiescent = !probe.loading && !probe.pressedDisabled && probe.fingerprint === lastFingerprint;
5806
6069
  if (quiescent) {
5807
6070
  stableCount++;
@@ -5833,7 +6096,7 @@ async function waitForLoadingSettle(opts = {}) {
5833
6096
  }
5834
6097
 
5835
6098
  // src/version.ts
5836
- var SDK_VERSION2 = "0.11.1";
6099
+ var SDK_VERSION2 = "0.11.3";
5837
6100
  var FiberSentinel = class extends React2.Component {
5838
6101
  componentDidMount() {
5839
6102
  const fiber = this._reactInternals ?? this._reactInternalFiber ?? null;
@@ -5977,6 +6240,21 @@ function probeNameMangling(components) {
5977
6240
  return _lastProbe;
5978
6241
  }
5979
6242
 
6243
+ // src/platform/textInputFocus.ts
6244
+ function createTextInputFocusHost(textInput) {
6245
+ const read = textInput?.State?.currentlyFocusedInput;
6246
+ if (typeof read !== "function") return null;
6247
+ return {
6248
+ focusedInstance() {
6249
+ try {
6250
+ return textInput.State.currentlyFocusedInput() ?? null;
6251
+ } catch {
6252
+ return null;
6253
+ }
6254
+ }
6255
+ };
6256
+ }
6257
+
5980
6258
  // src/platform/fabricGeometry.ts
5981
6259
  function readRect(instance) {
5982
6260
  const candidate = instance;
@@ -6107,6 +6385,10 @@ function AppilotsProviderInner({
6107
6385
  setGeometryHost(createFabricGeometryHost(Dimensions));
6108
6386
  return () => setGeometryHost(null);
6109
6387
  }, []);
6388
+ useEffect(() => {
6389
+ setFocusHost(createTextInputFocusHost(TextInput));
6390
+ return () => setFocusHost(null);
6391
+ }, []);
6110
6392
  const trackNativeDialogs = config.trackNativeDialogs !== false;
6111
6393
  useEffect(() => {
6112
6394
  if (!trackNativeDialogs) return;
@@ -6206,4 +6488,4 @@ function clearScreenRegistry() {
6206
6488
  screenRegistry.clear();
6207
6489
  }
6208
6490
 
6209
- export { ActionQueueMachine, AppilotsClient, AppilotsErrorBoundary, AppilotsProvider, AppilotsRegistryProvider, ChatSessionMachine, FORM_VALIDATION_TEXT_RE, FORM_WRITE_SUCCESS_TEXT_RE, OPTIONAL_STEP_AUTOMATION_HINT, RateLimitedError, SDK_VERSION2 as SDK_VERSION, TOUCHABLE_NAMES, _patchJsxRuntimes, actionPressTargetId, appilotsDebugLog, appilotsDebugWarn, attachElementIdsToChoiceGroups, clampSnapshotToWireLimits, clearAppilotsDebugTraces, clearScreenRegistry, componentRegistry, createComponentRegistry, createElementRegistry, createListRegistry, defaultDarkTheme, defaultLightTheme, deriveInteractionElements, describeAction, describeValue, elementRegistry, emptyDetectorCounts, enableAppilotsAutoTracking, findListItems, foldForCompare, geometryAvailability, geometryStats, getActiveRouteNames, getAllScreens, getAppilotsDebugTraces, getAutoTrackingState, getCurrentScreen, getCurrentScreenSignature, getDefaultRegistry, getFiberRoot, getGlobalConfig, getIntrospectionDiagnostics, getLastAnsweredNativeDialog, getLastWalkDiagnostics, getNameManglingProbe, getNavigationRef, getNavigationStateSnapshot, getOpenNativeDialog, getScreenMetadata, getTypeName, humanizeError, idLooselyMatches, initAppilots, isAlwaysListContainer, isAutoTrackingEnabled, isGenericSelectValue, isHeuristicListContainer, isListItemPressTarget, isModalFiber, isOptionalStepAutomationFailure, isReferenceable, isSwitchLike, listRegistry, looksDestructiveActionLabel, matchScore, mergeThemeTokens, normalize3, parseStableElementId, probeLoadingState, recordAppilotsDebugTrace, recordIntrospectionFailure, rectForNode, registerScreen, resolveBaseTheme, routesBelongToSameFeature, screenDepartedBaseline, setCurrentScreen, setNavigationRef, shouldSkipSubtree, snapshotShowsOpenCreateForm, subscribeAppilotsDebugTraces, switchValue, toWireElement, totalDetectorHits, useAppilotsContext, useResolvedRegistry, waitForLoadingSettle, waitForScreenSettle, walkFiber };
6491
+ export { ActionQueueMachine, AppilotsClient, AppilotsErrorBoundary, AppilotsProvider, AppilotsRegistryProvider, ChatSessionMachine, FORM_VALIDATION_TEXT_RE, FORM_WRITE_SUCCESS_TEXT_RE, OPTIONAL_STEP_AUTOMATION_HINT, RateLimitedError, SDK_VERSION2 as SDK_VERSION, TOUCHABLE_NAMES, _patchJsxRuntimes, actionPressTargetId, appilotsDebugLog, appilotsDebugWarn, attachElementIdsToChoiceGroups, clampSnapshotToWireLimits, clearAppilotsDebugTraces, clearScreenRegistry, componentRegistry, createComponentRegistry, createElementRegistry, createListRegistry, createSnapshotDeltaTracker, defaultDarkTheme, defaultLightTheme, deriveInteractionElements, describeAction, describeValue, elementRegistry, emptyDetectorCounts, enableAppilotsAutoTracking, findListItems, foldForCompare, geometryAvailability, geometryStats, getActiveRouteNames, getAllScreens, getAppilotsDebugTraces, getAutoTrackingState, getCurrentScreen, getCurrentScreenSignature, getDefaultRegistry, getFiberRoot, getGlobalConfig, getIntrospectionDiagnostics, getLastAnsweredNativeDialog, getLastWalkDiagnostics, getNameManglingProbe, getNavigationRef, getNavigationStateSnapshot, getOpenNativeDialog, getScreenMetadata, getTypeName, humanizeError, idLooselyMatches, initAppilots, isAlwaysListContainer, isAutoTrackingEnabled, isGenericSelectValue, isHeuristicListContainer, isListItemPressTarget, isModalFiber, isOptionalStepAutomationFailure, isReferenceable, isSwitchLike, listRegistry, looksDestructiveActionLabel, matchScore, mergeThemeTokens, normalize3, parseStableElementId, probeLoadingState, recordAppilotsDebugTrace, recordIntrospectionFailure, rectForNode, registerScreen, resolveBaseTheme, routesBelongToSameFeature, screenDepartedBaseline, setCurrentScreen, setNavigationRef, shouldSkipSubtree, snapshotShowsOpenCreateForm, subscribeAppilotsDebugTraces, switchValue, toWireElement, totalDetectorHits, useAppilotsContext, useResolvedRegistry, waitForLoadingSettle, waitForScreenSettle, walkFiber };