agent-device 0.20.8 → 0.20.10

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.
Files changed (220) hide show
  1. package/README.md +94 -31
  2. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.20.8.apk → agent-device-android-ime-helper-0.20.10.apk} +0 -0
  3. package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.10.apk.sha256 +1 -0
  4. package/android/ime-helper/dist/{agent-device-android-ime-helper-0.20.8.manifest.json → agent-device-android-ime-helper-0.20.10.manifest.json} +4 -4
  5. package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.20.8.apk → agent-device-android-snapshot-helper-0.20.10.apk} +0 -0
  6. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.10.apk.sha256 +1 -0
  7. package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.20.8.manifest.json → agent-device-android-snapshot-helper-0.20.10.manifest.json} +6 -6
  8. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.h +3 -0
  9. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerAXSnapshotBridge.m +13 -7
  10. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+AXSnapshotFallback.swift +10 -117
  11. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +4 -4
  12. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+FlatSnapshotFiltering.swift +103 -2
  13. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Interaction.swift +22 -23
  14. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +3 -23
  15. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Navigation.swift +10 -11
  16. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+PrivateAXPresentation.swift +108 -0
  17. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScrollGesture.swift +8 -5
  18. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Snapshot.swift +179 -73
  19. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotCapturePlan.swift +13 -12
  20. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotPresentation.swift +183 -0
  21. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SnapshotPresentationTests.swift +0 -0
  22. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SystemModal.swift +20 -18
  23. package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextInputCandidatePolicy.swift +19 -0
  24. package/dist/src/agent-browser-tool.js +1 -1
  25. package/dist/src/agent-device-client.js +2 -2
  26. package/dist/src/ai-sdk.d.ts +61 -0
  27. package/dist/src/ai-sdk.js +1 -0
  28. package/dist/src/android-adb.d.ts +6 -6
  29. package/dist/src/android-adb.js +1 -1
  30. package/dist/src/android.js +1 -1
  31. package/dist/src/app-helpers.js +1 -1
  32. package/dist/src/app-lifecycle.js +6 -7
  33. package/dist/src/app-lifecycle2.js +2 -2
  34. package/dist/src/app-log-reconnect.js +1 -1
  35. package/dist/src/app-log-runtime.js +3 -3
  36. package/dist/src/app-resolution.js +2 -2
  37. package/dist/src/app-state.js +2 -0
  38. package/dist/src/appearance.js +1 -0
  39. package/dist/src/apps.js +5 -5
  40. package/dist/src/artifacts.d.ts +1 -1
  41. package/dist/src/audio-probe.js +1 -1
  42. package/dist/src/auth-session.js +1 -1
  43. package/dist/src/backend-snapshot-options.js +1 -0
  44. package/dist/src/boot-diagnostics.js +2 -2
  45. package/dist/src/bounds.js +1 -0
  46. package/dist/src/byte-limit-stream.js +1 -0
  47. package/dist/src/cli-config.js +1 -1
  48. package/dist/src/cli-help.js +42 -30
  49. package/dist/src/cli-runner.js +1 -1
  50. package/dist/src/cli.js +7 -7
  51. package/dist/src/client-metro.js +1 -1
  52. package/dist/src/client-types.d.ts +1582 -0
  53. package/dist/src/command-catalog.js +1 -0
  54. package/dist/src/command-metadata.js +1 -1
  55. package/dist/src/command-schema.js +1 -1
  56. package/dist/src/command-surface.js +1 -1
  57. package/dist/src/command-tools.js +2 -0
  58. package/dist/src/config.js +1 -0
  59. package/dist/src/connection-runtime.js +1 -1
  60. package/dist/src/connection.js +3 -3
  61. package/dist/src/contracts.d.ts +1 -1
  62. package/dist/src/contracts.js +1 -1
  63. package/dist/src/daemon-shutdown-report.js +1 -1
  64. package/dist/src/daemon.js +1 -1
  65. package/dist/src/debug-symbols.js +3 -3
  66. package/dist/src/device-claim-inspection.js +1 -1
  67. package/dist/src/device-input-state.js +1 -1
  68. package/dist/src/device-ready.js +1 -1
  69. package/dist/src/device2.js +2 -2
  70. package/dist/src/dispatch.js +1 -1
  71. package/dist/src/entities.js +1 -1
  72. package/dist/src/find.js +1 -1
  73. package/dist/src/finders.js +1 -1
  74. package/dist/src/generic-settle.js +1 -1
  75. package/dist/src/generic.js +1 -1
  76. package/dist/src/harmonyos.js +1 -1
  77. package/dist/src/hdc.js +1 -1
  78. package/dist/src/helper-package-install.js +1 -0
  79. package/dist/src/helper.js +2 -2
  80. package/dist/src/host-process.js +1 -1
  81. package/dist/src/ime-lifecycle.js +2 -9
  82. package/dist/src/ime-recovery-marker.js +1 -0
  83. package/dist/src/index.d.ts +2 -1569
  84. package/dist/src/index.js +1 -1
  85. package/dist/src/input-actions.js +4 -4
  86. package/dist/src/input-actions2.js +1 -1
  87. package/dist/src/install-artifact.js +1 -1
  88. package/dist/src/install-artifact2.js +1 -1
  89. package/dist/src/install-source2.js +1 -1
  90. package/dist/src/interaction-common.js +1 -0
  91. package/dist/src/interaction-runtime.js +1 -1
  92. package/dist/src/interaction-snapshot.js +1 -1
  93. package/dist/src/interaction.js +1 -1
  94. package/dist/src/interaction2.js +1 -0
  95. package/dist/src/interactor.js +2 -2
  96. package/dist/src/interactor2.js +1 -1
  97. package/dist/src/interactors.js +1 -0
  98. package/dist/src/internal/bin.js +2 -2
  99. package/dist/src/internal/daemon.js +4 -7
  100. package/dist/src/internal/png-worker.js +1 -1
  101. package/dist/src/inventory.js +1 -1
  102. package/dist/src/keyed-lock.js +1 -0
  103. package/dist/src/lease-context.js +1 -1
  104. package/dist/src/lease.js +1 -1
  105. package/dist/src/limrun-runtime-dependencies.js +1 -1
  106. package/dist/src/limrun.d.ts +13 -3
  107. package/dist/src/limrun.js +1 -1
  108. package/dist/src/linux.js +1 -1
  109. package/dist/src/location-coordinates.js +1 -0
  110. package/dist/src/logcat.js +1 -1
  111. package/dist/src/manifest.js +1 -1
  112. package/dist/src/mobile-snapshot-semantics.js +1 -0
  113. package/dist/src/observability.js +1 -0
  114. package/dist/src/owner-identity.js +1 -1
  115. package/dist/src/perf.js +4 -0
  116. package/dist/src/physical-device-control.js +1 -1
  117. package/dist/src/platform-runtime-app-log-process.js +1 -1
  118. package/dist/src/platform-runtime-host.js +1 -1
  119. package/dist/src/platform-runtime-network-web-transport.js +1 -1
  120. package/dist/src/platform-runtime-open-target.js +1 -0
  121. package/dist/src/platform-runtime-operation-host.js +4 -4
  122. package/dist/src/platform-runtime-runtime-hints.js +2 -0
  123. package/dist/src/platform-runtime-screen-recording-apple-runner-host.js +1 -1
  124. package/dist/src/platform-runtime-screen-recording-apple-runner-transport.js +1 -1
  125. package/dist/src/platform-runtime-screen-recording-apple-transport.js +1 -1
  126. package/dist/src/platform-runtime-screen-recording-finalizer-host.js +1 -1
  127. package/dist/src/platform-runtime-screen-recording-web-host.js +1 -1
  128. package/dist/src/platform-runtime.js +1 -1
  129. package/dist/src/platform-runtime2.js +1 -0
  130. package/dist/src/plist.js +1 -1
  131. package/dist/src/png-resize.js +1 -0
  132. package/dist/src/png-worker-contract.js +1 -1
  133. package/dist/src/process-lock.js +1 -1
  134. package/dist/src/project-runtime.js +1 -0
  135. package/dist/src/provider-webdriver.js +1 -1
  136. package/dist/src/provider.js +1 -0
  137. package/dist/src/proxy.js +1 -1
  138. package/dist/src/react-native.js +1 -1
  139. package/dist/src/record-runtime.js +1 -1
  140. package/dist/src/record-trace.js +1 -1
  141. package/dist/src/recording-export-quality.js +1 -0
  142. package/dist/src/registry.js +29 -26
  143. package/dist/src/remote-config2.js +1 -1
  144. package/dist/src/replay.js +2 -2
  145. package/dist/src/reporting.js +5 -5
  146. package/dist/src/rolldown-runtime.js +1 -0
  147. package/dist/src/runner-client.js +1 -0
  148. package/dist/src/runner-disposal.js +1 -1
  149. package/dist/src/runner-lease.js +1 -1
  150. package/dist/src/runner-sequence.js +1 -1
  151. package/dist/src/runtime-transport.js +1 -1
  152. package/dist/src/runtime.js +3 -3
  153. package/dist/src/runtime2.js +4 -3
  154. package/dist/src/runtime3.js +1 -3
  155. package/dist/src/runtime4.js +3 -1
  156. package/dist/src/runtime5.js +1 -1
  157. package/dist/src/runtime6.js +1 -1
  158. package/dist/src/runtime7.js +1 -1
  159. package/dist/src/runtime8.js +1 -0
  160. package/dist/src/runtime9.js +1 -0
  161. package/dist/src/scoped-provider.js +1 -0
  162. package/dist/src/screenshot-density.js +1 -0
  163. package/dist/src/screenshot-result.js +4 -4
  164. package/dist/src/screenshot.js +1 -1
  165. package/dist/src/script-source-bundle.js +16 -0
  166. package/dist/src/sdk-android-adb.d.ts +44 -26
  167. package/dist/src/sdk-android-adb.js +4 -4
  168. package/dist/src/sdk-batch-runner.js +3 -3
  169. package/dist/src/sdk-contracts.d.ts +125 -88
  170. package/dist/src/sdk-contracts.js +2 -2
  171. package/dist/src/sdk-device.js +1 -1
  172. package/dist/src/sdk-finders.d.ts +1 -1
  173. package/dist/src/sdk-finders.js +1 -1
  174. package/dist/src/sdk-io.js +1 -1
  175. package/dist/src/sdk-remote-config.js +1 -1
  176. package/dist/src/sdk-selectors.d.ts +107 -95
  177. package/dist/src/sdk-selectors.js +1 -1
  178. package/dist/src/sdk.js +1 -0
  179. package/dist/src/selector-runtime.js +1 -1
  180. package/dist/src/selectors.d.ts +1 -1
  181. package/dist/src/selectors.js +1 -1
  182. package/dist/src/server.js +10 -3
  183. package/dist/src/session-target-evidence.js +1 -1
  184. package/dist/src/session.js +1 -30
  185. package/dist/src/session2.js +27 -0
  186. package/dist/src/setting-state.js +1 -1
  187. package/dist/src/shared.js +1 -1
  188. package/dist/src/simctl.js +1 -0
  189. package/dist/src/simulator-state.js +1 -0
  190. package/dist/src/simulator.js +1 -1
  191. package/dist/src/snapshot-runtime.js +1 -1
  192. package/dist/src/snapshot-scope.js +1 -0
  193. package/dist/src/snapshot-session.js +1 -1
  194. package/dist/src/snapshot.js +8 -1
  195. package/dist/src/snapshot2.js +1 -1
  196. package/dist/src/snapshot3.js +1 -0
  197. package/dist/src/src.js +1 -1
  198. package/dist/src/src2.js +1 -2
  199. package/dist/src/src3.js +2 -1
  200. package/dist/src/src4.js +1 -0
  201. package/dist/src/string-enum.js +1 -0
  202. package/dist/src/tool-provider.js +3 -3
  203. package/dist/src/tool-provider2.js +1 -1
  204. package/dist/src/tool-provider3.js +1 -1
  205. package/dist/src/update-check.js +1 -1
  206. package/dist/src/verdict.js +1 -0
  207. package/dist/src/verified-file.js +1 -0
  208. package/dist/src/version.js +1 -1
  209. package/dist/src/video.js +1 -1
  210. package/dist/src/web.js +1 -1
  211. package/package.json +27 -10
  212. package/server.json +3 -3
  213. package/android/ime-helper/dist/agent-device-android-ime-helper-0.20.8.apk.sha256 +0 -1
  214. package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.20.8.apk.sha256 +0 -1
  215. package/dist/src/interaction-touch-reference-frame.js +0 -1
  216. package/dist/src/notifications.js +0 -1
  217. package/dist/src/platform-runtime-toolchain-host.js +0 -1
  218. package/dist/src/prepare-kind.js +0 -15
  219. package/dist/src/selector-vocabulary.js +0 -1
  220. package/dist/src/text-surface.js +0 -1
@@ -229,21 +229,23 @@ extension RunnerTests {
229
229
  let identifier = element.identifier.trimmingCharacters(in: .whitespacesAndNewlines)
230
230
  let valueText = String(describing: element.value ?? "")
231
231
  .trimmingCharacters(in: .whitespacesAndNewlines)
232
- let node = SnapshotNode(
233
- index: 0,
234
- type: elementTypeName(element.elementType),
235
- label: label.isEmpty ? nil : label,
236
- identifier: identifier.isEmpty ? nil : identifier,
237
- value: valueText.isEmpty ? nil : valueText,
238
- rect: snapshotRect(from: element.frame),
239
- enabled: element.isEnabled,
240
- focused: nil,
241
- selected: element.isSelected ? true : nil,
242
- hittable: element.isHittable,
243
- depth: 0,
244
- parentIndex: nil,
245
- hiddenContentAbove: nil,
246
- hiddenContentBelow: nil
232
+ let node = SnapshotPresentation.singleElementRead(
233
+ RawAXNode(
234
+ index: 0,
235
+ type: elementTypeName(element.elementType),
236
+ label: label.isEmpty ? nil : label,
237
+ identifier: identifier.isEmpty ? nil : identifier,
238
+ value: valueText.isEmpty ? nil : valueText,
239
+ rect: snapshotRect(from: element.frame),
240
+ enabled: element.isEnabled,
241
+ focused: nil,
242
+ selected: element.isSelected ? true : nil,
243
+ hittable: element.isHittable,
244
+ depth: 0,
245
+ parentIndex: nil,
246
+ hiddenContentAbove: nil,
247
+ hiddenContentBelow: nil
248
+ )
247
249
  )
248
250
  return Response(
249
251
  ok: true,
@@ -317,7 +319,11 @@ extension RunnerTests {
317
319
  .filter { element in
318
320
  guard element.exists else { return false }
319
321
  let frame = element.frame
320
- return !frame.isEmpty && frameContainsPoint(frame, point, tolerance: 2)
322
+ return isCoordinateTextInputCandidate(
323
+ enabled: element.isEnabled,
324
+ frame: frame,
325
+ point: point
326
+ )
321
327
  }
322
328
  .sorted { left, right in
323
329
  let leftArea = max(1, left.frame.width * left.frame.height)
@@ -336,13 +342,6 @@ extension RunnerTests {
336
342
  }
337
343
  }
338
344
 
339
- private func frameContainsPoint(_ frame: CGRect, _ point: CGPoint, tolerance: CGFloat) -> Bool {
340
- point.x >= frame.minX - tolerance
341
- && point.x <= frame.maxX + tolerance
342
- && point.y >= frame.minY - tolerance
343
- && point.y <= frame.maxY + tolerance
344
- }
345
-
346
345
  private func readableText(for element: XCUIElement) -> String? {
347
346
  let label = element.label.trimmingCharacters(in: .whitespacesAndNewlines)
348
347
  let identifier = element.identifier.trimmingCharacters(in: .whitespacesAndNewlines)
@@ -235,7 +235,7 @@ struct DataPayload: Codable {
235
235
  let text: String?
236
236
  let found: Bool?
237
237
  let items: [String]?
238
- let nodes: [SnapshotNode]?
238
+ let nodes: [PresentedNode]?
239
239
  let truncated: Bool?
240
240
  let snapshotQuality: SnapshotQuality?
241
241
  let gestureStartUptimeMs: Double?
@@ -277,7 +277,7 @@ struct DataPayload: Codable {
277
277
  text: String? = nil,
278
278
  found: Bool? = nil,
279
279
  items: [String]? = nil,
280
- nodes: [SnapshotNode]? = nil,
280
+ nodes: [PresentedNode]? = nil,
281
281
  truncated: Bool? = nil,
282
282
  snapshotQuality: SnapshotQuality? = nil,
283
283
  gestureStartUptimeMs: Double? = nil,
@@ -375,27 +375,7 @@ struct SnapshotRect: Codable {
375
375
  let height: Double
376
376
  }
377
377
 
378
- struct SnapshotNode: Codable {
379
- let index: Int
380
- let type: String
381
- let label: String?
382
- let identifier: String?
383
- let value: String?
384
- let rect: SnapshotRect
385
- let enabled: Bool
386
- let focused: Bool?
387
- let selected: Bool?
388
- let hittable: Bool
389
- let depth: Int
390
- let parentIndex: Int?
391
- let hiddenContentAbove: Bool?
392
- let hiddenContentBelow: Bool?
393
- /// Names of the element's UIAccessibilityCustomActions, present only when the
394
- /// capture asked for them and the element carries some.
395
- var actions: [String]? = nil
396
- }
397
-
398
- struct SnapshotOptions {
378
+ struct PresentationOptions {
399
379
  let interactiveOnly: Bool
400
380
  let depth: Int?
401
381
  let scope: String?
@@ -81,17 +81,16 @@ extension RunnerTests {
81
81
  return navigationBackKeywords.firstIndex { text.contains($0) }
82
82
  }
83
83
 
84
+ // isFinite/>0 alone don't reject CGRect.infinite — its origin (~-9e307) is finite.
85
+ static func isUsableNavigationFrame(_ frame: CGRect) -> Bool {
86
+ guard frame.width.isFinite, frame.height.isFinite, frame.width > 0, frame.height > 0 else {
87
+ return false
88
+ }
89
+ return !frame.isInfinite
90
+ }
91
+
84
92
  static func isTopNavigationControlFrame(_ candidate: CGRect, in window: CGRect) -> Bool {
85
- guard
86
- candidate.width.isFinite,
87
- candidate.height.isFinite,
88
- window.width.isFinite,
89
- window.height.isFinite,
90
- candidate.width > 0,
91
- candidate.height > 0,
92
- window.width > 0,
93
- window.height > 0
94
- else {
93
+ guard isUsableNavigationFrame(candidate), isUsableNavigationFrame(window) else {
95
94
  return false
96
95
  }
97
96
  // Accept the compact navigation/search header band without matching deep content controls.
@@ -100,7 +99,7 @@ extension RunnerTests {
100
99
  }
101
100
 
102
101
  static func topLeadingNavigationFallbackPoint(in frame: CGRect) -> CGPoint? {
103
- guard frame.width.isFinite, frame.height.isFinite, frame.width > 0, frame.height > 0 else {
102
+ guard isUsableNavigationFrame(frame) else {
104
103
  return nil
105
104
  }
106
105
  // Aim at the standard leading navigation slot, bounded for compact and tablet widths.
@@ -0,0 +1,108 @@
1
+ import XCTest
2
+
3
+ extension RunnerTests {
4
+ private static let privateAXProjectionTolerance: CGFloat = 1
5
+
6
+ func privateAXPresentation(rawRoot: [String: Any], options: PresentationOptions, viewport: CGRect)
7
+ -> [RawAXNode]
8
+ {
9
+ var nodes: [RawAXNode] = []
10
+ var hints: [Int: (above: Bool, below: Bool)] = [:]
11
+ appendPrivateAXNode(rawRoot, to: &nodes, hints: &hints, options: options, viewport: viewport,
12
+ depth: 0, parentIndex: nil, insideMatchedScope: false, scrollContext: nil,
13
+ projectionCursor: .root)
14
+ return applyHiddenContentHints(hints, to: nodes)
15
+ }
16
+
17
+ private func appendPrivateAXNode(_ raw: [String: Any], to nodes: inout [RawAXNode],
18
+ hints: inout [Int: (above: Bool, below: Bool)], options: PresentationOptions, viewport: CGRect,
19
+ depth: Int, parentIndex: Int?, insideMatchedScope: Bool,
20
+ scrollContext: (index: Int, rect: CGRect)?, projectionCursor: FlatSnapshotProjectionCursor)
21
+ {
22
+ if let limit = options.depth, depth > limit { return }
23
+ let rect = privateAXRect(raw["frame"])
24
+ let label = privateAXPresentationString(raw["label"])
25
+ let identifier = privateAXPresentationString(raw["identifier"])
26
+ let value = privateAXPresentationString(raw["value"])
27
+ let rawType = privateAXPresentationInt(raw["type"]) ?? 0
28
+ let enabled = privateAXPresentationBool(raw["enabled"]) ?? true
29
+ let children = raw["children"] as? [[String: Any]] ?? []
30
+ let hasSemanticContent = [label, identifier, value].contains(where: { !$0.isEmpty })
31
+ let elementType = flatSnapshotElementType(rawElementType: rawType)
32
+ let hasFrame = !rect.isNull && !rect.isEmpty
33
+ let negligibleDecoration = parentIndex != nil
34
+ && !hasSemanticContent
35
+ && (!hasFrame
36
+ || rect.width <= Self.privateAXProjectionTolerance
37
+ || rect.height <= Self.privateAXProjectionTolerance)
38
+ let onScreen = hasFrame
39
+ && isVisibleInRegularSnapshot(
40
+ rect,
41
+ viewport: viewport,
42
+ scrollContainerAnchor: scrollContext
43
+ )
44
+ let projectionTransition = flatSnapshotProjectionTransition(
45
+ frame: rect,
46
+ intersectsViewportAndScrollClip: onScreen,
47
+ elementType: elementType,
48
+ hasChildren: !children.isEmpty,
49
+ cursor: projectionCursor
50
+ )
51
+ let projection = projectionTransition.decision
52
+ let presentationVisible = projection.presentationVisible && !negligibleDecoration
53
+ let decision = flatSnapshotFilterDecision(
54
+ FlatSnapshotFilterNode(isRoot: parentIndex == nil, label: label, identifier: identifier,
55
+ valueText: value.isEmpty ? nil : value, visible: presentationVisible),
56
+ options: options, visibilityPolicy: .viewportProjected,
57
+ insideMatchedScope: insideMatchedScope)
58
+ let include = decision.include
59
+
60
+ if let hiddenFrame = projectionTransition.hiddenContentFrame, let scrollContext {
61
+ rememberHiddenContentHint(for: hiddenFrame, relativeTo: scrollContext, hints: &hints)
62
+ }
63
+
64
+ let currentIndex: Int?
65
+ if include {
66
+ currentIndex = nodes.count
67
+ let typeName = elementType.map(elementTypeName) ?? "Element(\(rawType))"
68
+ nodes.append(RawAXNode(index: nodes.count, type: typeName,
69
+ label: label.isEmpty ? nil : label, identifier: identifier.isEmpty ? nil : identifier,
70
+ value: value.isEmpty ? nil : value, rect: snapshotRect(from: rect), enabled: enabled,
71
+ focused: privateAXPresentationBool(raw["focused"]) == true ? true : nil,
72
+ selected: privateAXPresentationBool(raw["selected"]) == true ? true : nil,
73
+ hittable: onScreen && enabled && privateAXInteractiveCandidate(rawElementType: rawType),
74
+ depth: depth, parentIndex: parentIndex, hiddenContentAbove: nil, hiddenContentBelow: nil,
75
+ actions: raw["actions"] as? [String]))
76
+ } else { currentIndex = parentIndex }
77
+
78
+ let nextScrollContext: (index: Int, rect: CGRect)?
79
+ if include, let elementType, let currentIndex {
80
+ nextScrollContext = scrollContainerAnchor(
81
+ for: elementType,
82
+ hasChildren: !children.isEmpty,
83
+ visible: onScreen,
84
+ frame: rect,
85
+ nodeIndex: currentIndex
86
+ ) ?? scrollContext
87
+ } else { nextScrollContext = scrollContext }
88
+ for child in children {
89
+ appendPrivateAXNode(child, to: &nodes, hints: &hints, options: options, viewport: viewport,
90
+ depth: depth + 1, parentIndex: currentIndex,
91
+ insideMatchedScope: decision.insideMatchedScope, scrollContext: nextScrollContext,
92
+ projectionCursor: projection.descendants)
93
+ }
94
+ }
95
+
96
+ private func privateAXPresentationString(_ value: Any?) -> String {
97
+ guard let value else { return "" }
98
+ return (value as? String ?? String(describing: value))
99
+ .trimmingCharacters(in: .whitespacesAndNewlines)
100
+ }
101
+ private func privateAXPresentationInt(_ value: Any?) -> Int? {
102
+ (value as? Int) ?? (value as? NSNumber)?.intValue
103
+ }
104
+ private func privateAXPresentationBool(_ value: Any?) -> Bool? {
105
+ (value as? Bool) ?? (value as? NSNumber)?.boolValue
106
+ }
107
+ }
108
+
@@ -3,10 +3,10 @@ import XCTest
3
3
  // Swift port of buildScrollGesturePlan from packages/contracts/src/scroll-gesture.ts.
4
4
  //
5
5
  // This is a deliberate two-place invariant: the daemon keeps the TS implementation (for Android,
6
- // recording, and reported-pixels), and the runner places the gesture with this Swift copy. The
7
- // parity test vectors at the bottom of this file mirror
8
- // packages/contracts/src/scroll-gesture.test.ts —
9
- // if you change the math in either language, update the other and both vector sets.
6
+ // recording, and reported-pixels), and the runner places the gesture with this Swift copy. Both
7
+ // ports are asserted against the same table, contracts/fixtures/scroll-gesture.json (gated
8
+ // XCTest at the bottom of this file, vitest twin packages/contracts/src/scroll-gesture.test.ts) —
9
+ // if you change the math in either language, update the other and the table.
10
10
  //
11
11
  // All inputs here are positive (reference dims, travel, center), so Swift's `.rounded()`
12
12
  // (half away from zero) matches JS `Math.round` (half up) on every value computed below.
@@ -20,7 +20,10 @@ struct RunnerScrollGesturePlan {
20
20
  }
21
21
 
22
22
  private let runnerDefaultScrollAmount = 0.6
23
- private let runnerDefaultEdgePaddingFraction = 0.05
23
+ // Both constants are pinned by contracts/fixtures/scroll-gesture.json (`constants`). Scroll gestures
24
+ // stay out of the outer 10% of each axis so a saturated scroll never touches down inside the status
25
+ // bar / Dynamic Island band (#1781 A1).
26
+ private let runnerDefaultEdgePaddingFraction = 0.1
24
27
 
25
28
  func runnerScrollGesturePlan(
26
29
  direction: String,