@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.1

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 (284) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/LICENSE +0 -10
  3. package/docs/facts.json +1 -1
  4. package/manifest.json +285 -285
  5. package/package.json +3 -3
  6. package/pipeline/lib/redact.mjs +3 -2
  7. package/pipeline/scripts/_notices.mjs +1 -1
  8. package/pipeline/scripts/gen-skills-index.mjs +13 -1
  9. package/pipeline/scripts/pre-commit-check.sh +4 -0
  10. package/pipeline/skills/.skill-manifest.json +69 -69
  11. package/pipeline/skills/shared/README.md +70 -70
  12. package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
  13. package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
  14. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
  15. package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
  16. package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
  17. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
  18. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
  19. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
  20. package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
  21. package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
  22. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
  23. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +345 -277
  24. package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
  25. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +107 -121
  26. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +145 -165
  27. package/pipeline/skills/shared/external/app-store-review/SKILL.md +306 -326
  28. package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
  29. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
  30. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
  31. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +335 -360
  32. package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
  33. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
  34. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
  35. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
  36. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
  37. package/pipeline/skills/shared/external/authentication/SKILL.md +277 -381
  38. package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
  39. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +135 -178
  40. package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
  41. package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
  42. package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
  43. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
  44. package/pipeline/skills/shared/external/background-processing/SKILL.md +274 -384
  45. package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
  46. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +173 -321
  47. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
  48. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
  49. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
  50. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
  51. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
  52. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
  53. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
  54. package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
  55. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
  56. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +228 -376
  57. package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
  58. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
  59. package/pipeline/skills/shared/external/core-data/SKILL.md +302 -368
  60. package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
  61. package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
  62. package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
  63. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
  64. package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
  65. package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
  66. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
  67. package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
  68. package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
  69. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
  70. package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
  71. package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
  72. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
  73. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
  74. package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
  75. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
  76. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
  77. package/pipeline/skills/shared/external/device-integrity/SKILL.md +236 -353
  78. package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
  79. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
  80. package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
  81. package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
  82. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
  83. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
  84. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
  85. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
  86. package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
  87. package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
  88. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
  89. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
  90. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
  91. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
  92. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
  93. package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
  94. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
  95. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
  96. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
  97. package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
  98. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
  99. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
  100. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
  101. package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
  102. package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
  103. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
  104. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
  105. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
  106. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
  107. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
  108. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
  109. package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
  110. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
  111. package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
  112. package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
  113. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
  114. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +3 -3
  115. package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +1 -1
  116. package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +8 -7
  117. package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +2 -2
  118. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +5 -2
  119. package/pipeline/skills/shared/external/localization-reuse-map/scripts/_shared.py +100 -0
  120. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +45 -26
  121. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +14 -16
  122. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +12 -5
  123. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +2 -1
  124. package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +6 -5
  125. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +44 -18
  126. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +5 -2
  127. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +10 -11
  128. package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +4 -33
  129. package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +12 -59
  130. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +297 -267
  131. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
  132. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
  133. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
  134. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
  135. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
  136. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
  137. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
  138. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
  139. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
  140. package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
  141. package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
  142. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
  143. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
  144. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
  145. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
  146. package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
  147. package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
  148. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
  149. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
  150. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
  151. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
  152. package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
  153. package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
  154. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
  155. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
  156. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
  157. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
  158. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
  159. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
  160. package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
  161. package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
  162. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
  163. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
  164. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
  165. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
  166. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
  167. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
  168. package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
  169. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
  170. package/pipeline/skills/shared/external/skill-creator/template.md +7 -1
  171. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
  172. package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
  173. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
  174. package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
  175. package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
  176. package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
  177. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +302 -241
  178. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
  179. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
  180. package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
  181. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
  182. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
  183. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
  184. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
  185. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
  186. package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
  187. package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
  188. package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
  189. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
  190. package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
  191. package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
  192. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +304 -351
  193. package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
  194. package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
  195. package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
  196. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
  197. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
  198. package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
  199. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
  200. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
  201. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
  202. package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
  203. package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
  204. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
  205. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
  206. package/pipeline/skills/shared/external/swift-security/SKILL.md +183 -162
  207. package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
  208. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
  209. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +411 -476
  210. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
  211. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
  212. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
  213. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
  214. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
  215. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
  216. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +375 -491
  217. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
  218. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
  219. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
  220. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +397 -457
  221. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
  222. package/pipeline/skills/shared/external/swift-testing/SKILL.md +191 -175
  223. package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
  224. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +81 -84
  225. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
  226. package/pipeline/skills/shared/external/swiftdata/SKILL.md +394 -256
  227. package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
  228. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
  229. package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
  230. package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
  231. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
  232. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
  233. package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
  234. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
  235. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
  236. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
  237. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
  238. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
  239. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
  240. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
  241. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
  242. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
  243. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
  244. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
  245. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
  246. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
  247. package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
  248. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
  249. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
  250. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
  251. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +201 -168
  252. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
  253. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +134 -133
  254. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
  255. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +111 -138
  256. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
  257. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
  258. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
  259. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
  260. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
  261. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
  262. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
  263. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
  264. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
  265. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
  266. package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
  267. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
  268. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
  269. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
  270. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
  271. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
  272. package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
  273. package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
  274. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
  275. package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
  276. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
  277. package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
  278. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
  279. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
  280. package/pipeline/skills/shared/external/weatherkit/SKILL.md +160 -315
  281. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
  282. package/pipeline/skills/shared/external/widgetkit/SKILL.md +224 -288
  283. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +416 -719
  284. package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
@@ -1,76 +1,62 @@
1
1
  ---
2
2
  name: callkit-voip
3
- description: "Implement VoIP calling with CallKit and PushKit. Use when building incoming/outgoing call flows, registering for VoIP push notifications, configuring CXProvider and CXCallController, handling call actions, coordinating audio sessions, or creating Call Directory extensions for caller ID and call blocking."
3
+ description: "CallKit and PushKit VoIP calling: CXProvider and CXCallController, incoming and outgoing call flows, call actions, VoIP push registration and reporting, call audio sessions, Call Directory caller ID and blocking. Use when implementing VoIP calls or VoIP push handling."
4
4
  metadata:
5
- source: "dpearson2699/swift-ios-skills (PolyForm Perimeter 1.0.0)"
5
+ source: multi-agent-pipeline
6
6
  ---
7
7
 
8
- # CallKit
8
+ # CallKit and PushKit VoIP
9
9
 
10
- Build VoIP calling features that integrate with the native iOS call UI using
11
- CallKit and PushKit. Covers incoming/outgoing call flows, VoIP push
12
- registration, audio session coordination, and call directory extensions.
13
- Targets Swift 6.3 / iOS 26+.
10
+ CallKit puts an app's VoIP calls into the same system call UI that cellular
11
+ calls use: the lock-screen answer slider, the in-call screen, Recents and
12
+ Bluetooth controls. PushKit wakes the app when a call arrives. Everything here
13
+ targets Swift 6.3 and iOS 26 or later unless a line names another release.
14
14
 
15
- ## Contents
16
-
17
- - [Setup](#setup)
18
- - [Provider Configuration](#provider-configuration)
19
- - [Incoming Call Flow](#incoming-call-flow)
20
- - [Outgoing Call Flow](#outgoing-call-flow)
21
- - [PushKit VoIP Registration](#pushkit-voip-registration)
22
- - [Audio Session Coordination](#audio-session-coordination)
23
- - [Call Directory Extension and Manager](#call-directory-extension-and-manager)
24
- - [Common Mistakes](#common-mistakes)
25
- - [Review Checklist](#review-checklist)
26
- - [References](#references)
15
+ Rotating APNs auth keys and everyday remote-notification registration live
16
+ in `push-notifications`. This skill starts at the VoIP push type.
27
17
 
28
18
  ## Setup
29
19
 
30
- ### Project Configuration
20
+ ### Project configuration
31
21
 
32
- 1. Enable the **Voice over IP** background mode in Signing & Capabilities
33
- 2. Add the **Push Notifications** capability
34
- 3. For call directory extensions, add a **Call Directory Extension** target
22
+ 1. Signing & Capabilities: turn on Background Modes and tick **Voice over IP**.
23
+ 2. Push Notifications must also appear in the capability list.
24
+ 3. Caller ID or blocking lists need a separate target of type Call Directory Extension.
35
25
 
36
- ### Key Types
26
+ ### The types you touch
37
27
 
38
- | Type | Role |
39
- |---|---|
40
- | `CXProvider` | Reports calls to the system, receives call actions |
41
- | `CXCallController` | Requests call actions (start, end, hold, mute) |
42
- | `CXCallUpdate` | Describes call metadata (caller name, video, handle) |
43
- | `CXProviderDelegate` | Handles system call actions and audio session events |
44
- | `PKPushRegistry` | Registers for and receives VoIP push notifications |
45
- | `PKVoIPPushMetadata` | iOS 26.4+ metadata that says whether a VoIP push must be reported |
28
+ | Type | Job |
29
+ | --- | --- |
30
+ | `CXProvider` | Tells the system about calls and receives the actions the system wants performed |
31
+ | `CXCallController` | Asks the system to perform an action: start, end, hold, mute |
32
+ | `CXCallUpdate` | Describes a call: caller name, handle, video |
33
+ | `CXProviderDelegate` | Performs call actions and receives audio session activation |
34
+ | `PKPushRegistry` | Registers for VoIP pushes and delivers them |
35
+ | `PKVoIPPushMetadata` | iOS 26.4+: says whether a given VoIP push has to be reported |
46
36
 
47
- ## Provider Configuration
37
+ ## Provider configuration
48
38
 
49
- Create a single `CXProvider` at app launch and keep it alive for the app
50
- lifetime. Configure it with a `CXProviderConfiguration` that describes your
51
- calling capabilities.
39
+ Build one `CXProvider` when the app launches and keep that instance until the
40
+ process dies. Creating a second provider or recreating it per call confuses the
41
+ system about which calls exist.
52
42
 
53
43
  ```swift
54
44
  import CallKit
55
45
 
56
- /// CXProvider dispatches all delegate calls to the queue passed to `setDelegate(_:queue:)`.
57
- /// The `let` properties are initialized once and never mutated, making this type
58
- /// safe to share across concurrency domains despite @unchecked Sendable.
59
- final class CallManager: NSObject, @unchecked Sendable {
60
- static let shared = CallManager()
46
+ @MainActor
47
+ final class PhoneLine: NSObject {
48
+ static let shared = PhoneLine()
61
49
 
62
50
  let provider: CXProvider
63
- let callController = CXCallController()
51
+ let controller = CXCallController()
64
52
 
65
- private override init() {
53
+ override init() {
66
54
  let config = CXProviderConfiguration()
67
- config.localizedName = "My VoIP App"
68
55
  config.supportsVideo = true
69
- config.maximumCallsPerCallGroup = 1
70
56
  config.maximumCallGroups = 2
57
+ config.maximumCallsPerCallGroup = 1
71
58
  config.supportedHandleTypes = [.phoneNumber, .emailAddress]
72
59
  config.includesCallsInRecents = true
73
-
74
60
  provider = CXProvider(configuration: config)
75
61
  super.init()
76
62
  provider.setDelegate(self, queue: nil)
@@ -78,204 +64,188 @@ final class CallManager: NSObject, @unchecked Sendable {
78
64
  }
79
65
  ```
80
66
 
81
- ## Incoming Call Flow
67
+ `PhoneLine` is `@MainActor` because every delegate callback arrives on the
68
+ queue passed to `setDelegate(_:queue:)`, and `nil` means the main queue. The
69
+ delegate conformance below is `@preconcurrency`, so those callbacks run as
70
+ main-actor code and the `Task` each one starts inherits that isolation. That is
71
+ what lets a `Task` hold on to the non-`Sendable` `CXAction` and `CXProvider`
72
+ it was handed; from a nonisolated method Swift 6 rejects the same capture with
73
+ "passing closure as a 'sending' parameter risks causing data races". The name the
74
+ system shows comes from `localizedName`; on current SDKs it is read from the
75
+ bundle display name, and the old `init(localizedName:)` is deprecated.
82
76
 
83
- When a required VoIP call push arrives, report the incoming call to CallKit
84
- immediately. The system displays the native call UI. You must report required
85
- calls before the PushKit completion handler returns -- failure to do so causes
86
- the system to terminate your app.
77
+ ## Incoming call flow
78
+
79
+ When a VoIP push says a call is ringing, report it to CallKit right away; the
80
+ system then draws the native incoming-call UI. For a push that must be
81
+ reported, the report has to happen before the PushKit completion handler runs,
82
+ or the system kills the app.
87
83
 
88
84
  ```swift
89
- func reportIncomingCall(
90
- uuid: UUID,
91
- handle: String,
92
- hasVideo: Bool
93
- ) async throws {
94
- let update = CXCallUpdate()
95
- update.remoteHandle = CXHandle(type: .phoneNumber, value: handle)
96
- update.hasVideo = hasVideo
97
- update.localizedCallerName = "Jane Doe"
98
-
99
- try await withCheckedThrowingContinuation {
100
- (continuation: CheckedContinuation<Void, Error>) in
101
- provider.reportNewIncomingCall(
102
- with: uuid,
103
- update: update
104
- ) { error in
105
- if let error {
106
- continuation.resume(throwing: error)
107
- } else {
108
- continuation.resume()
85
+ extension PhoneLine {
86
+ func announceIncoming(id: UUID, number: String, name: String, video: Bool) async throws {
87
+ let update = CXCallUpdate()
88
+ update.remoteHandle = CXHandle(type: .phoneNumber, value: number.trimmingCharacters(in: .whitespaces))
89
+ update.localizedCallerName = name
90
+ update.hasVideo = video
91
+
92
+ try await withCheckedThrowingContinuation { (cont: CheckedContinuation<Void, Error>) in
93
+ provider.reportNewIncomingCall(with: id, update: update) { error in
94
+ if let error { cont.resume(throwing: error) } else { cont.resume() }
109
95
  }
110
96
  }
111
97
  }
112
98
  }
113
99
  ```
114
100
 
115
- ### Handling the Answer Action
101
+ The report throws when Do Not Disturb or the user's block list filters the call.
102
+ That is a normal outcome, not a crash path.
116
103
 
117
- Implement `CXProviderDelegate` to respond when the user answers:
104
+ ### Answering and ending
118
105
 
119
106
  ```swift
120
- extension CallManager: CXProviderDelegate {
121
- func providerDidReset(_ provider: CXProvider) {
122
- // End all calls, reset audio
107
+ extension PhoneLine: @preconcurrency CXProviderDelegate {
108
+ func providerDidReset(_ cx: CXProvider) {
109
+ MediaSession.shared.hangUpEverything()
110
+ AudioRoute.tearDown()
123
111
  }
124
112
 
125
- func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
126
- // Prepare audio, then fulfill only after the call is actually ready
127
- configureAudioSession()
128
- connectToCallServer(callUUID: action.callUUID) { success in
129
- if success {
130
- action.fulfill()
131
- } else {
132
- provider.reportCall(
133
- with: action.callUUID,
134
- endedAt: Date(),
135
- reason: .failed
136
- )
137
- action.fail()
113
+ func provider(_ cx: CXProvider, perform answer: CXAnswerCallAction) {
114
+ AudioRoute.prepareForCall()
115
+ Task {
116
+ do {
117
+ try await SignalingClient.shared.join(call: answer.callUUID)
118
+ answer.fulfill()
119
+ } catch {
120
+ cx.reportCall(with: answer.callUUID, endedAt: .now, reason: .failed)
121
+ answer.fail()
138
122
  }
139
123
  }
140
124
  }
141
125
 
142
- func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
143
- disconnectFromCallServer(callUUID: action.callUUID)
144
- action.fulfill()
126
+ func provider(_ cx: CXProvider, perform hangup: CXEndCallAction) {
127
+ Task {
128
+ await SignalingClient.shared.leave(call: hangup.callUUID)
129
+ hangup.fulfill()
130
+ }
145
131
  }
146
132
  }
147
133
  ```
148
134
 
149
- ## Outgoing Call Flow
135
+ `providerDidReset(_:)` means the system dropped every call: end all of them and
136
+ reset audio. The answer action stays pending until the media server connection
137
+ is up; only then is it fulfilled. If the connection fails, report the call as
138
+ ended with `.failed` and fail the action.
139
+
140
+ ## Outgoing call flow
150
141
 
151
- Use `CXCallController` to request an outgoing call. The system routes the
152
- request through your `CXProviderDelegate`.
142
+ Outgoing calls start with a request to `CXCallController`. The system checks it
143
+ and hands it back through `provider(_:perform:)` with a `CXStartCallAction`.
153
144
 
154
145
  ```swift
155
- func startOutgoingCall(handle: String, hasVideo: Bool) {
156
- let uuid = UUID()
157
- let handle = CXHandle(type: .phoneNumber, value: handle)
158
- let startAction = CXStartCallAction(call: uuid, handle: handle)
159
- startAction.isVideo = hasVideo
160
-
161
- let transaction = CXTransaction(action: startAction)
162
- callController.request(transaction) { error in
163
- if let error {
164
- print("Failed to start call: \(error)")
146
+ extension PhoneLine {
147
+ func dial(_ address: String, video: Bool) -> UUID {
148
+ let id = UUID()
149
+ let start = CXStartCallAction(call: id, handle: CXHandle(type: .phoneNumber, value: address))
150
+ start.isVideo = video
151
+ controller.request(CXTransaction(action: start)) { error in
152
+ if let error { Log.calls.error("dial refused: \(error)") }
165
153
  }
154
+ return id
166
155
  }
167
- }
168
- ```
169
156
 
170
- ### Delegate Methods for Outgoing Calls
171
-
172
- ```swift
173
- extension CallManager {
174
- func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
175
- configureAudioSession()
176
- // Begin connecting to server
177
- provider.reportOutgoingCall(
178
- with: action.callUUID,
179
- startedConnectingAt: Date()
180
- )
181
-
182
- connectToServer(callUUID: action.callUUID) {
183
- provider.reportOutgoingCall(
184
- with: action.callUUID,
185
- connectedAt: Date()
186
- )
157
+ func provider(_ cx: CXProvider, perform dial: CXStartCallAction) {
158
+ AudioRoute.prepareForCall()
159
+ let id = dial.callUUID
160
+ cx.reportOutgoingCall(with: id, startedConnectingAt: .now)
161
+ Task {
162
+ do {
163
+ try await SignalingClient.shared.place(call: id, to: dial.handle.value)
164
+ cx.reportOutgoingCall(with: id, connectedAt: .now)
165
+ dial.fulfill()
166
+ } catch {
167
+ dial.fail()
168
+ }
187
169
  }
188
- action.fulfill()
189
170
  }
190
171
  }
191
172
  ```
192
173
 
193
- ## PushKit VoIP Registration
174
+ Report both `startedConnectingAt` and `connectedAt`; Recents and the call timer
175
+ use them.
176
+
177
+ ## PushKit VoIP registration
178
+
179
+ Create the registry on every launch, not lazily, and forward each token change
180
+ to your server.
181
+
182
+ Report rules:
194
183
 
195
- Register for VoIP pushes at every app launch and send token changes to your
196
- server. For iOS 13 SDK+ apps, every report-required VoIP call push must be
197
- reported before PushKit completion using CallKit, or LiveCommunicationKit for
198
- apps built on that framework. On iOS 26.4+, `PKVoIPPushMetadata.mustReport` is
199
- the gate: `true` means report before completion; `false` means no CallKit or
200
- LiveCommunicationKit report is required. Missing a required report before
201
- completion can terminate the app, and repeated failures may stop VoIP delivery.
184
+ - Linked against SDK 13 or newer: every VoIP push that is a call must be
185
+ reported to CallKit (LiveCommunicationKit apps report there instead)
186
+ before the PushKit completion handler is called.
187
+ - iOS 26.4 and later: `PKVoIPPushMetadata.mustReport` decides. When it is `true`,
188
+ the report precedes completion; when `false`, that push can be handled
189
+ without any report at all.
190
+ - A skipped required report may end the process; keep skipping and the
191
+ system may stop delivering VoIP pushes altogether.
202
192
 
203
- | Path | Report decision | Completion timing |
204
- |---|---|---|
205
- | iOS 26.4+ `mustReport == true` | Report with CallKit or LiveCommunicationKit | After report callback |
206
- | iOS 26.4+ `mustReport == false` | No CallKit/LiveCommunicationKit report required | After local handling |
207
- | Older delegate | iOS 13 SDK+ treats VoIP call pushes as report-required | After report callback |
193
+ | Delivery path | Report? | When to call completion |
194
+ | --- | --- | --- |
195
+ | 26.4+ delegate, `mustReport == true` | Yes | After the report callback |
196
+ | 26.4+ delegate, `mustReport == false` | No | After local handling |
197
+ | Older delegate | Treat every VoIP call push as required | After the report callback |
208
198
 
209
199
  ```swift
210
200
  import PushKit
211
201
 
212
- final class PushManager: NSObject, PKPushRegistryDelegate {
213
- let registry: PKPushRegistry
202
+ @MainActor
203
+ final class VoIPInbox: NSObject, @preconcurrency PKPushRegistryDelegate {
204
+ private let registry = PKPushRegistry(queue: .main)
214
205
 
215
- override init() {
216
- registry = PKPushRegistry(queue: .main)
217
- super.init()
206
+ func start() {
218
207
  registry.delegate = self
219
208
  registry.desiredPushTypes = [.voIP]
220
209
  }
221
210
 
222
- func pushRegistry(
223
- _ registry: PKPushRegistry,
224
- didUpdate pushCredentials: PKPushCredentials,
225
- for type: PKPushType
226
- ) {
227
- let token = pushCredentials.token
228
- .map { String(format: "%02x", $0) }
229
- .joined()
230
- // Send token to your server
231
- sendTokenToServer(token)
211
+ func pushRegistry(_ reg: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
212
+ let hex = credentials.token.reduce(into: "") { out, byte in out += String(format: "%02x", byte) }
213
+ Backend.shared.upload(voipToken: hex)
232
214
  }
233
215
 
234
216
  @available(iOS 26.4, *)
235
- func pushRegistry(
236
- _ registry: PKPushRegistry,
237
- didReceiveIncomingVoIPPushWith payload: PKPushPayload,
238
- metadata: PKVoIPPushMetadata,
239
- withCompletionHandler completion: @escaping @Sendable () -> Void
240
- ) {
217
+ func pushRegistry(_ reg: PKPushRegistry,
218
+ didReceiveIncomingVoIPPushWith payload: PKPushPayload,
219
+ metadata: PKVoIPPushMetadata,
220
+ withCompletionHandler completion: @escaping @Sendable () -> Void) {
241
221
  guard metadata.mustReport else {
242
222
  completion()
243
223
  return
244
224
  }
245
- handleIncomingVoIPPush(payload, completion: completion)
225
+ ring(from: payload, then: completion)
246
226
  }
247
227
 
248
- // Keep the older callback for iOS 26.0-26.3 and older deployment targets.
249
- func pushRegistry(
250
- _ registry: PKPushRegistry,
251
- didReceiveIncomingPushWith payload: PKPushPayload,
252
- for type: PKPushType,
253
- completion: @escaping () -> Void
254
- ) {
228
+ func pushRegistry(_ reg: PKPushRegistry,
229
+ didReceiveIncomingPushWith payload: PKPushPayload,
230
+ for type: PKPushType,
231
+ completion: @escaping () -> Void) {
255
232
  guard type == .voIP else {
256
233
  completion()
257
234
  return
258
235
  }
259
-
260
- handleIncomingVoIPPush(payload, completion: completion)
236
+ ring(from: payload, then: completion)
261
237
  }
262
238
 
263
- private func handleIncomingVoIPPush(
264
- _ payload: PKPushPayload,
265
- completion: @escaping () -> Void
266
- ) {
267
- let callUUID = UUID()
268
- let handle = payload.dictionaryPayload["handle"] as? String ?? "Unknown"
269
-
239
+ private func ring(from payload: PKPushPayload, then completion: @escaping () -> Void) {
240
+ let body = payload.dictionaryPayload
241
+ let number = body["from"] as? String ?? "Unknown"
242
+ let name = body["display"] as? String ?? number
243
+ let video = body["video"] as? Bool ?? false
270
244
  Task {
271
245
  do {
272
- try await CallManager.shared.reportIncomingCall(
273
- uuid: callUUID,
274
- handle: handle,
275
- hasVideo: false
276
- )
246
+ try await PhoneLine.shared.announceIncoming(id: UUID(), number: number, name: name, video: video)
277
247
  } catch {
278
- // Call was filtered by DND or block list
248
+ Log.calls.notice("call filtered or rejected: \(error)")
279
249
  }
280
250
  completion()
281
251
  }
@@ -283,218 +253,167 @@ final class PushManager: NSObject, PKPushRegistryDelegate {
283
253
  }
284
254
  ```
285
255
 
286
- Server-side VoIP pushes should use a short lifetime: set `apns-expiration` to
287
- `0` or only a few seconds. After the initial push wakes the app, send hangups
288
- and call-detail changes over the app-server connection instead of sending more
289
- VoIP pushes.
256
+ Keep the older delegate method for iOS 26.0 through 26.3 and for lower
257
+ deployment targets. The registry delivers on `.main`, which is why the inbox is
258
+ `@MainActor`: the `Task` in `ring` can then carry the non-`Sendable` completion
259
+ handler without leaving the actor. Completion runs after the report attempt on both the
260
+ success and the error path.
290
261
 
291
- ## Audio Session Coordination
262
+ Server side: send VoIP pushes with `apns-expiration: 0` or a few seconds; a
263
+ ring that arrives a minute late is worse than none. After the first push has
264
+ woken the app, hangups and changed call details travel over your own socket or
265
+ API channel instead of further VoIP pushes.
292
266
 
293
- CallKit manages audio session activation/deactivation. Configure your audio
294
- session when CallKit tells you to, not before.
295
- Review answers should name both sides: start media only in
296
- `provider(_:didActivate:)`, and stop/tear down media in
297
- `provider(_:didDeactivate:)` or reset paths.
267
+ ## Audio session coordination
298
268
 
299
- ```swift
300
- extension CallManager {
301
- func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
302
- // Audio session is now active -- start audio engine / WebRTC
303
- startAudioEngine()
304
- }
269
+ CallKit owns activation and deactivation of the audio session. Set the category
270
+ while preparing the call, and wait for CallKit before touching media.
305
271
 
306
- func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
307
- // Audio session deactivated -- stop audio engine
308
- stopAudioEngine()
309
- }
272
+ ```swift
273
+ import AVFAudio
310
274
 
311
- func configureAudioSession() {
312
- let session = AVAudioSession.sharedInstance()
275
+ enum AudioRoute {
276
+ static func prepareForCall() {
313
277
  do {
314
- try session.setCategory(
315
- .playAndRecord,
316
- mode: .voiceChat,
317
- options: [.allowBluetooth, .allowBluetoothA2DP]
318
- )
278
+ try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .voiceChat,
279
+ options: [.allowBluetoothHFP, .allowBluetoothA2DP])
319
280
  } catch {
320
- print("Audio session configuration failed: \(error)")
281
+ Log.calls.error("audio category failed: \(error)")
321
282
  }
322
283
  }
323
- }
324
- ```
325
-
326
- ## Call Directory Extension and Manager
327
284
 
328
- Use Call Directory for preloaded caller ID/blocking, not per-call API lookup.
329
- The extension loads sorted bulk data in `beginRequest(with:)`; the main app uses
330
- `CXCallDirectoryManager` to check enabled status, open Call Blocking &
331
- Identification settings when disabled, and reload after data changes. Store
332
- `CXCallDirectoryPhoneNumber` as country code plus digits in ascending order
333
- (for example `18005551234`), not a formatted string.
334
-
335
- ```swift
336
- import CallKit
285
+ static func tearDown() { MediaSession.shared.stopEngine() }
286
+ }
337
287
 
338
- final class CallDirectoryHandler: CXCallDirectoryProvider {
339
- override func beginRequest(
340
- with context: CXCallDirectoryExtensionContext
341
- ) {
342
- if context.isIncremental {
343
- addOrRemoveIncrementalEntries(to: context)
344
- } else {
345
- addAllEntries(to: context)
346
- }
347
- context.completeRequest()
288
+ extension PhoneLine {
289
+ func provider(_ cx: CXProvider, didActivate session: AVAudioSession) {
290
+ MediaSession.shared.startEngine()
348
291
  }
349
292
 
350
- private func addAllEntries(
351
- to context: CXCallDirectoryExtensionContext
352
- ) {
353
- // Country code + digits, sorted in ascending order
354
- let blockedNumbers: [CXCallDirectoryPhoneNumber] = [
355
- 18005551234, 18005555678
356
- ]
357
- for number in blockedNumbers {
358
- context.addBlockingEntry(
359
- withNextSequentialPhoneNumber: number
360
- )
361
- }
362
-
363
- let identifiedNumbers: [(CXCallDirectoryPhoneNumber, String)] = [
364
- (18005551111, "Local Pizza"),
365
- (18005552222, "Dentist Office")
366
- ]
367
- for (number, label) in identifiedNumbers {
368
- context.addIdentificationEntry(
369
- withNextSequentialPhoneNumber: number,
370
- label: label
371
- )
372
- }
293
+ func provider(_ cx: CXProvider, didDeactivate session: AVAudioSession) {
294
+ MediaSession.shared.stopEngine()
373
295
  }
374
296
  }
375
297
  ```
376
298
 
377
- ### Main-App Manager: Status, Settings, Reload
299
+ Media starts in `provider(_:didActivate:)` and stops in
300
+ `provider(_:didDeactivate:)` or a reset path. A review should point at both
301
+ callbacks by name.
302
+
303
+ ## Call Directory extension and manager
304
+
305
+ A Call Directory supplies caller ID labels and blocked numbers that are loaded
306
+ in advance. It cannot look anything up while a call is ringing.
378
307
 
379
308
  ```swift
380
- let manager = CXCallDirectoryManager.sharedInstance
381
- manager.getEnabledStatusForExtension(withIdentifier: extensionID) { status, _ in
382
- guard status == .enabled else {
383
- manager.openSettings { _ in } // Call Blocking & Identification
384
- return
309
+ import CallKit
310
+
311
+ final class SpamListProvider: CXCallDirectoryProvider {
312
+ override func beginRequest(with ctx: CXCallDirectoryExtensionContext) {
313
+ if ctx.isIncremental {
314
+ applyChanges(to: ctx)
315
+ } else {
316
+ for number in SharedStore.blocked().sorted() {
317
+ ctx.addBlockingEntry(withNextSequentialPhoneNumber: number)
318
+ }
319
+ for entry in SharedStore.labels().sorted(by: { $0.key < $1.key }) {
320
+ ctx.addIdentificationEntry(withNextSequentialPhoneNumber: entry.key, label: entry.value)
321
+ }
322
+ }
323
+ ctx.completeRequest()
385
324
  }
386
- manager.reloadExtension(withIdentifier: extensionID) { _ in }
387
325
  }
388
326
  ```
389
327
 
390
- Check `getEnabledStatusForExtension(...)` before assuming the extension is
391
- active, use `openSettings(...)` for Call Blocking & Identification when
392
- disabled, and call `reloadExtension(...)` after data changes. Route APNs
393
- auth-key rotation and normal remote-notification setup to push-notifications.
394
-
395
- ## Common Mistakes
396
-
397
- ### DON'T: Fail to report a required call on VoIP push receipt
398
-
399
- Follow the PushKit report rules above: iOS 13 SDK+ apps must report
400
- report-required VoIP call pushes before completion, and on iOS 26.4+
401
- `PKVoIPPushMetadata.mustReport` identifies which pushes are required. Missing a
402
- required report can terminate the app; repeated failures may stop VoIP delivery.
403
-
404
- Do not treat a required VoIP push as a data-only notification. Report the call
405
- to CallKit and call the PushKit completion handler from the report completion.
406
-
407
- ### DON'T: Fulfill answer before the call is connected
408
-
409
- When the user answers before your app has established the server/media
410
- connection, leave the `CXAnswerCallAction` pending while connecting. Fulfill it
411
- after the call is ready; if connection fails, fail the action and report the
412
- call ended with `.failed`.
413
-
414
- ### DON'T: Start audio before CallKit activates the session
415
-
416
- Starting your audio engine before `provider(_:didActivate:)` causes silence
417
- or immediate deactivation. CallKit manages session priority with the system.
418
-
419
- Prepare audio in the answer/start action if needed, then start media only from
420
- `provider(_:didActivate:)`.
421
-
422
- For iOS 26 call translation, set `CXProviderConfiguration.supportsAudioTranslation`
423
- when your service supports it and handle `CXSetTranslatingCallAction`. If a
424
- person mutes during a translated call, mute app input with
425
- `CXSetMutedCallAction`; do not deactivate upstream audio that translated audio
426
- depends on.
427
-
428
- For encrypted VoIP metadata, use `CXProvider.reportNewIncomingVoIPPushPayload`
429
- only from a notification service extension when the server cannot determine
430
- whether encrypted content is a VoIP call or other data. That path requires the
431
- `com.apple.developer.usernotifications.filtering` entitlement; otherwise send a
432
- normal PushKit VoIP push.
433
-
434
- ### DON'T: Forget to call action.fulfill() or action.fail()
435
-
436
- Failing to fulfill or fail an action leaves the call in a limbo state and
437
- triggers the timeout handler.
438
-
439
- Every provider action path must eventually call `fulfill()` or `fail()`,
440
- including network-error and cancellation paths.
441
-
442
- ### DON'T: Ignore push token refresh
443
-
444
- The VoIP push token can change at any time. If your server has a stale token,
445
- pushes silently fail and incoming calls never arrive.
446
-
447
- Send the token to your server every time `didUpdate pushCredentials` fires,
448
- not just during first-run onboarding.
449
-
450
- ### DON'T: Use Call Directory for per-call lookup
451
-
452
- Call Directory extensions provide preloaded caller ID and blocking data. They
453
- cannot ask a web service for the incoming caller during call presentation.
454
- Fetch or generate the dataset ahead of time, reload the extension, and add
455
- entries in sorted sequential order.
456
-
457
- ## Review Checklist
458
-
459
- - [ ] VoIP background mode enabled in capabilities
460
- - [ ] Single `CXProvider` instance created at app launch and retained
461
- - [ ] `CXProviderDelegate` set before reporting any calls
462
- - [ ] iOS 26.4+ PushKit path reports when `mustReport` is true and may skip when false
463
- - [ ] iOS 13 SDK+ PushKit VoIP call pushes report to CallKit before completion
464
- - [ ] VoIP APNs requests use `apns-expiration` of `0` or only a few seconds
465
- - [ ] Hangups and detail updates use the app-server connection after the initial push
466
- - [ ] `action.fulfill()` or `action.fail()` called for every provider delegate action
467
- - [ ] `CXAnswerCallAction` fulfilled only after the call server/media connection is ready
468
- - [ ] Audio engine started only after `provider(_:didActivate:)` callback
469
- - [ ] Audio engine stopped in `provider(_:didDeactivate:)` callback
470
- - [ ] Audio session category set to `.playAndRecord` with `.voiceChat` mode
471
- - [ ] VoIP push token sent to server on every `didUpdate pushCredentials` callback
472
- - [ ] `PKPushRegistry` created at every app launch (not lazily)
473
- - [ ] Call Directory data is preloaded, not fetched per incoming call
474
- - [ ] `CXCallDirectoryPhoneNumber` documented as country calling code + digits
475
- - [ ] `CXCallDirectoryManager` names status check, reload, and settings-opening APIs
476
- - [ ] `CXCallUpdate` populated with `localizedCallerName` and `remoteHandle`
477
- - [ ] Outgoing calls report `startedConnectingAt` and `connectedAt` timestamps
478
- - [ ] iOS 26 call translation keeps upstream audio active during mute
479
- - [ ] Encrypted metadata filtering mentions the notification service extension entitlement
328
+ A `CXCallDirectoryPhoneNumber` is an `Int64`: country calling code followed by
329
+ the national digits, for example `14155550123`, never a formatted string.
330
+ Entries go in ascending order.
331
+
332
+ In the containing app, use `CXCallDirectoryManager.sharedInstance`:
333
+
334
+ - `getEnabledStatusForExtension(withIdentifier:)` before assuming the extension
335
+ is on.
336
+ - When the status is not `.enabled`, `openSettings { _ in }` takes the user to
337
+ Call Blocking & Identification.
338
+ - After the dataset changes, `reloadExtension(withIdentifier:)`.
339
+
340
+ Rotating APNs keys and the usual notification registration are covered by
341
+ `push-notifications`.
342
+
343
+ ## Common mistakes
344
+
345
+ - **Treating a call push as data.** A required VoIP call push that is not
346
+ reported ends the process. Hand it to CallKit first and let the report's
347
+ callback be where PushKit's completion runs.
348
+ - **Fulfilling the answer too early.** Keep `CXAnswerCallAction` pending while
349
+ connecting; fulfill once media is ready; on failure, fail it and report the
350
+ call ended with `.failed`.
351
+ - **Starting audio before activation.** Media started before
352
+ `provider(_:didActivate:)` plays silence or gets deactivated at once, because
353
+ CallKit arbitrates session priority. Prepare in the answer or start action,
354
+ play only after activation.
355
+ - **Missing iOS 26 call translation.** Set
356
+ `CXProviderConfiguration.supportsAudioTranslation` when the app supports it
357
+ and handle `CXSetTranslatingCallAction`.
358
+ - **Muting by killing audio.** During a translated call, mute the app's own
359
+ input in `CXSetMutedCallAction`; do not deactivate the upstream audio that
360
+ translation reads.
361
+ - **Overusing encrypted payload reporting.** `CXProvider.reportNewIncomingVoIPPushPayload`
362
+ belongs in a notification service extension, and only when the server cannot
363
+ tell whether encrypted content is a call. If it can tell, send a normal
364
+ PushKit VoIP push. The extension needs the
365
+ entitlement `com.apple.developer.usernotifications.filtering`.
366
+ - **Leaving an action unresolved.** A call action that is neither fulfilled nor
367
+ failed hangs until its timeout fires. Every branch, including network errors
368
+ and cancellation, ends with `fulfill()` or `fail()`.
369
+ - **Caching the push token.** It can change at any moment; a stale server copy
370
+ makes pushes vanish silently. Upload on every `didUpdate` credentials call.
371
+ - **Per-call lookup in Call Directory.** The extension cannot call a web service
372
+ when a call is presented. Fetch ahead, reload, and add sorted sequential
373
+ entries.
374
+
375
+ ## Review checklist
376
+
377
+ - [ ] Voice over IP background mode is on
378
+ - [ ] Exactly one `CXProvider`, created at launch and retained
379
+ - [ ] Provider delegate is set before any call is reported
380
+ - [ ] On 26.4+, a `mustReport` of true triggers a report; false allows skipping it
381
+ - [ ] With SDK 13 or newer, each VoIP call push is reported ahead of completion
382
+ - [ ] `apns-expiration` is 0 or a few seconds
383
+ - [ ] After the waking push, hangups and call details use the app's own server channel
384
+ - [ ] Every delegate action ends in `fulfill()` or `fail()`
385
+ - [ ] Answer is fulfilled only when the server and media connection are ready
386
+ - [ ] Audio engine starts after `didActivate` and stops in `didDeactivate`
387
+ - [ ] Audio category is `.playAndRecord` with mode `.voiceChat`
388
+ - [ ] Token uploaded on every `didUpdate` credentials callback
389
+ - [ ] `PKPushRegistry` created on every launch, not lazily
390
+ - [ ] Call Directory data is preloaded, never fetched per call
391
+ - [ ] `CXCallDirectoryPhoneNumber` described as country calling code plus digits
392
+ - [ ] `CXCallDirectoryManager` status check, reload and settings APIs are named
393
+ - [ ] `CXCallUpdate` sets `localizedCallerName` and `remoteHandle`
394
+ - [ ] Dialed calls send both `startedConnectingAt` and `connectedAt` timestamps
395
+ - [ ] Translated calls keep upstream audio live while muted
396
+ - [ ] Encrypted payload filtering lists the filtering entitlement the extension needs
480
397
 
481
398
  ## References
482
399
 
483
- - Extended patterns (hold, mute, group calls, delegate lifecycle): [references/callkit-patterns.md](references/callkit-patterns.md)
484
- - [CallKit framework](https://sosumi.ai/documentation/callkit)
485
- - [CXProvider](https://sosumi.ai/documentation/callkit/cxprovider)
486
- - [CXCallController](https://sosumi.ai/documentation/callkit/cxcallcontroller)
487
- - [CXCallAction](https://sosumi.ai/documentation/callkit/cxcallaction)
488
- - [CXCallUpdate](https://sosumi.ai/documentation/callkit/cxcallupdate)
489
- - [CXProviderConfiguration](https://sosumi.ai/documentation/callkit/cxproviderconfiguration)
490
- - [CXProviderDelegate](https://sosumi.ai/documentation/callkit/cxproviderdelegate)
491
- - [PKPushRegistry](https://sosumi.ai/documentation/pushkit/pkpushregistry)
492
- - [PKPushRegistryDelegate](https://sosumi.ai/documentation/pushkit/pkpushregistrydelegate)
493
- - [PKVoIPPushMetadata](https://sosumi.ai/documentation/pushkit/pkvoippushmetadata)
494
- - [CXCallDirectoryProvider](https://sosumi.ai/documentation/callkit/cxcalldirectoryprovider)
495
- - [CXCallDirectoryPhoneNumber](https://sosumi.ai/documentation/callkit/cxcalldirectoryphonenumber)
496
- - [CXCallDirectoryManager](https://sosumi.ai/documentation/callkit/cxcalldirectorymanager)
497
- - [CXSetTranslatingCallAction](https://sosumi.ai/documentation/callkit/cxsettranslatingcallaction)
498
- - [reportNewIncomingVoIPPushPayload(_:completion:)](https://sosumi.ai/documentation/callkit/cxprovider/reportnewincomingvoippushpayload(_:completion:))
499
- - [Making and receiving VoIP calls](https://sosumi.ai/documentation/callkit/making-and-receiving-voip-calls)
500
- - [Responding to VoIP Notifications from PushKit](https://sosumi.ai/documentation/pushkit/responding-to-voip-notifications-from-pushkit)
400
+ - [Longer patterns](references/callkit-patterns.md): observable manager, hold
401
+ and mute, concurrent calls, call observer, encrypted filtering, incremental
402
+ Call Directory, local testing
403
+ - [CallKit](https://developer.apple.com/documentation/callkit)
404
+ - [CXProvider](https://developer.apple.com/documentation/callkit/cxprovider)
405
+ - [CXCallController](https://developer.apple.com/documentation/callkit/cxcallcontroller)
406
+ - [CXCallAction](https://developer.apple.com/documentation/callkit/cxcallaction)
407
+ - [CXCallUpdate](https://developer.apple.com/documentation/callkit/cxcallupdate)
408
+ - [CXProviderConfiguration](https://developer.apple.com/documentation/callkit/cxproviderconfiguration)
409
+ - [CXProviderDelegate](https://developer.apple.com/documentation/callkit/cxproviderdelegate)
410
+ - [CXSetTranslatingCallAction](https://developer.apple.com/documentation/callkit/cxsettranslatingcallaction)
411
+ - [reportNewIncomingVoIPPushPayload(_:completion:)](https://developer.apple.com/documentation/callkit/cxprovider/reportnewincomingvoippushpayload(_:completion:))
412
+ - [CXCallDirectoryProvider](https://developer.apple.com/documentation/callkit/cxcalldirectoryprovider)
413
+ - [CXCallDirectoryPhoneNumber](https://developer.apple.com/documentation/callkit/cxcalldirectoryphonenumber)
414
+ - [CXCallDirectoryManager](https://developer.apple.com/documentation/callkit/cxcalldirectorymanager)
415
+ - [PKPushRegistry](https://developer.apple.com/documentation/pushkit/pkpushregistry)
416
+ - [PKPushRegistryDelegate](https://developer.apple.com/documentation/pushkit/pkpushregistrydelegate)
417
+ - [PKVoIPPushMetadata](https://developer.apple.com/documentation/pushkit/pkvoippushmetadata)
418
+ - Article: [VoIP call handling with CallKit](https://developer.apple.com/documentation/callkit/making-and-receiving-voip-calls)
419
+ - Article: [PushKit VoIP notification handling](https://developer.apple.com/documentation/pushkit/responding-to-voip-notifications-from-pushkit)