@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +0 -10
  3. package/docs/facts.json +1 -1
  4. package/manifest.json +266 -267
  5. package/package.json +2 -2
  6. package/pipeline/scripts/_notices.mjs +1 -1
  7. package/pipeline/skills/.skill-manifest.json +68 -68
  8. package/pipeline/skills/shared/README.md +70 -70
  9. package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
  10. package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
  11. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
  12. package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
  13. package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
  14. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
  15. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
  16. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
  17. package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
  18. package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
  19. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
  20. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
  21. package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
  22. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
  23. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
  24. package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
  25. package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
  26. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
  27. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
  28. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
  29. package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
  30. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
  31. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
  32. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
  33. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
  34. package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
  35. package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
  36. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
  37. package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
  38. package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
  39. package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
  40. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
  41. package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
  42. package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
  43. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
  44. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
  45. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
  46. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
  47. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
  48. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
  49. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
  50. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
  51. package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
  52. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
  53. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
  54. package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
  55. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
  56. package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
  57. package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
  58. package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
  59. package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
  60. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
  61. package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
  62. package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
  63. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
  64. package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
  65. package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
  66. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
  67. package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
  68. package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
  69. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
  70. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
  71. package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
  72. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
  73. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
  74. package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
  75. package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
  76. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
  77. package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
  78. package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
  79. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
  80. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
  81. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
  82. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
  83. package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
  84. package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
  85. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
  86. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
  87. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
  88. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
  89. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
  90. package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
  91. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
  92. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
  93. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
  94. package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
  95. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
  96. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
  97. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
  98. package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
  99. package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
  100. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
  101. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
  102. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
  103. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
  104. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
  105. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
  106. package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
  107. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
  108. package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
  109. package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
  110. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
  111. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
  112. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
  113. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
  114. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
  115. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
  116. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
  117. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
  118. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
  119. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
  120. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
  121. package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
  122. package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
  123. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
  124. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
  125. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
  126. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
  127. package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
  128. package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
  129. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
  130. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
  131. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
  132. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
  133. package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
  134. package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
  135. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
  136. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
  137. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
  138. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
  139. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
  140. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
  141. package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
  142. package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
  143. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
  144. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
  145. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
  146. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
  147. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
  148. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
  149. package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
  150. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
  151. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
  152. package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
  153. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
  154. package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
  155. package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
  156. package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
  157. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
  158. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
  159. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
  160. package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
  161. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
  162. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
  163. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
  164. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
  165. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
  166. package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
  167. package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
  168. package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
  169. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
  170. package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
  171. package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
  172. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
  173. package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
  174. package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
  175. package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
  176. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
  177. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
  178. package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
  179. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
  180. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
  181. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
  182. package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
  183. package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
  184. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
  185. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
  186. package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
  187. package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
  188. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
  189. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
  190. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
  191. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
  192. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
  193. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
  194. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
  195. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
  196. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
  197. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
  198. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
  199. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
  200. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
  201. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
  202. package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
  203. package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
  204. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
  205. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
  206. package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
  207. package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
  208. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
  209. package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
  210. package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
  211. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
  212. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
  213. package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
  214. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
  215. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
  216. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
  217. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
  218. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
  219. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
  220. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
  221. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
  222. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
  223. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
  224. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
  225. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
  226. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
  227. package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
  228. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
  229. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
  230. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
  231. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
  232. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
  233. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
  234. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
  235. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
  236. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
  237. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
  238. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
  239. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
  240. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
  241. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
  242. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
  243. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
  244. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
  245. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
  246. package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
  247. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
  248. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
  249. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
  250. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
  251. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
  252. package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
  253. package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
  254. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
  255. package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
  256. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
  257. package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
  258. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
  259. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
  260. package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
  261. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
  262. package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
  263. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
  264. package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
@@ -3,41 +3,42 @@
3
3
  "evals": [
4
4
  {
5
5
  "id": 1,
6
- "prompt": "Audit this proposed MusicKit setup for an iOS 26 app: the team adds a com.apple.developer.musickit entitlement in Xcode, skips NSAppleMusicUsageDescription, calls MusicAuthorization.request() during launch, and says authorization will crash when the entitlement is missing. Write a concise review with corrected setup and authorization guidance.",
7
- "expected_output": "A review that replaces the invented entitlement guidance with MusicKit App Service setup for an explicit bundle ID, requires NSAppleMusicUsageDescription, and explains authorization behavior without overstating crashes.",
6
+ "prompt": "We are adding Apple Music to a running-coach app. What do we have to configure in the portal and in the project before MusicKit works, how should we ask for permission, and do we need anything extra so music keeps going when the screen locks? A teammate says we also need the com.apple.developer.musickit entitlement or authorization will crash.",
7
+ "expected_output": "A setup answer that names the App Service and usage string, explains request() versus currentStatus, treats background audio as conditional, and rejects the entitlement claim.",
8
8
  "files": [],
9
9
  "assertions": [
10
- "Recommends enabling the MusicKit App Service for the app's explicit bundle ID so automatic developer token generation is configured.",
11
- "Requires NSAppleMusicUsageDescription before accessing the user's music data or media library.",
12
- "Explains that MusicAuthorization.request() presents the system consent dialog when necessary and currentStatus can be checked without prompting.",
13
- "Does not claim that com.apple.developer.musickit is the required iOS app entitlement or that a missing entitlement causes authorization to crash.",
14
- "Mentions UIBackgroundModes audio only when the app needs background playback."
10
+ "Says to enable the MusicKit App Service on the app's explicit App ID so developer tokens are generated automatically",
11
+ "Requires NSAppleMusicUsageDescription in Info.plist",
12
+ "Explains that MusicAuthorization.request() prompts only when needed and MusicAuthorization.currentStatus reads the state without prompting",
13
+ "Does not claim a com.apple.developer.musickit entitlement is required on iOS or that its absence crashes authorization",
14
+ "Adds audio to UIBackgroundModes only because playback must continue in the background"
15
15
  ]
16
16
  },
17
17
  {
18
18
  "id": 2,
19
- "prompt": "Implement focused SwiftUI snippets for a MusicKit search-and-play flow: search Apple Music songs, play a selected catalog song with ApplicationMusicPlayer, and offer Apple Music subscription signup when playback is unavailable.",
20
- "expected_output": "Swift snippets that separate catalog search from subscription playback, gate playback on MusicSubscription.current.canPlayCatalogContent, use ApplicationMusicPlayer, and present the Music subscription offer only when the user can become a subscriber.",
19
+ "prompt": "Build the flow where a user searches the Apple Music catalog, taps a song and it plays inside our app. Users without Apple Music should be invited to subscribe instead of seeing a broken play button.",
20
+ "expected_output": "Catalog search, a subscription gate before playback, app-scoped playback, and a correctly gated subscription offer.",
21
21
  "files": [],
22
22
  "assertions": [
23
- "Uses MusicCatalogSearchRequest for catalog search and does not require subscription just to fetch catalog search results.",
24
- "Checks MusicSubscription.current.canPlayCatalogContent before attempting catalog playback.",
25
- "Uses ApplicationMusicPlayer.shared with queue assignment and play() for app-scoped playback.",
26
- "Checks canBecomeSubscriber before presenting the Apple Music subscription offer.",
27
- "Uses musicSubscriptionOffer(isPresented:options:onLoadCompletion:) or accurately notes the options and load-completion parameters for the offer sheet."
23
+ "Searches with MusicCatalogSearchRequest and does not require a subscription for searching",
24
+ "Gates playback on MusicSubscription.current canPlayCatalogContent",
25
+ "Plays through ApplicationMusicPlayer.shared by setting the queue and calling play()",
26
+ "Checks canBecomeSubscriber before showing the offer",
27
+ "Presents the offer with musicSubscriptionOffer(isPresented:options:onLoadCompletion:)"
28
28
  ]
29
29
  },
30
30
  {
31
31
  "id": 3,
32
- "prompt": "Review a MusicKit implementation that fetches top songs with MusicCatalogChartsRequest(kinds: [.mostPlayed], types: [Song.self]), adds songs and playlists to the user's library without checking cloud library state, and manually updates MPNowPlayingInfoCenter for Apple Music playback. Explain what should change.",
33
- "expected_output": "A review that uses the current chart request initializer shape, gates library writes on authorization and iCloud Music Library availability, and keeps Now Playing guidance scoped to custom audio versus MusicKit playback.",
32
+ "prompt": "Add a Top Charts screen, a button that saves a song to the user's library, and make sure the Lock Screen shows what is playing. Should we also build a CarPlay scene and an AVKit player while we are at it?",
33
+ "expected_output": "Charts with an explicit genre argument, guarded library writes, correct Now Playing ownership, and no unrelated AVKit or CarPlay work.",
34
34
  "files": [],
35
35
  "assertions": [
36
- "Uses or recommends MusicCatalogChartsRequest(genre:kinds:types:) with an explicit genre argument such as nil.",
37
- "Checks MusicAuthorization.currentStatus or requests authorization before library mutations.",
38
- "Checks MusicSubscription.current.hasCloudLibraryEnabled before adding songs, albums, or playlist entries to the user's library.",
39
- "Explains that ApplicationMusicPlayer handles Now Playing metadata automatically for Apple Music content.",
40
- "Keeps MediaPlayer guidance limited to custom audio or remote command handling and does not broaden into unrelated AVKit or CarPlay implementation advice."
36
+ "Uses MusicCatalogChartsRequest(genre:kinds:types:) and passes genre explicitly, for example nil",
37
+ "Confirms authorization before library mutations",
38
+ "Checks hasCloudLibraryEnabled before adding to the library",
39
+ "States that ApplicationMusicPlayer publishes Now Playing automatically for Apple Music content",
40
+ "Limits MediaPlayer guidance to custom audio metadata and remote commands",
41
+ "Does not add AVKit player or CarPlay scene implementation"
41
42
  ]
42
43
  }
43
44
  ]
@@ -1,383 +1,294 @@
1
- # MusicKit + MediaPlayer Extended Patterns
1
+ # MusicKit Patterns
2
2
 
3
- Overflow reference for the `musickit` skill. Contains advanced patterns that exceed the main skill file's scope.
3
+ Longer examples for the `musickit-audio` skill. Its SKILL.md covers setup,
4
+ authorization, subscription checks and the basic player; this file builds on
5
+ that.
4
6
 
5
- ## Contents
7
+ ## SwiftUI integration
6
8
 
7
- - [MusicKit SwiftUI Integration](#musickit-swiftui-integration)
8
- - [Genre and Chart Browsing](#genre-and-chart-browsing)
9
- - [Library Management](#library-management)
10
- - [Playlist Access](#playlist-access)
11
- - [Now Playing Session](#now-playing-session)
12
- - [Background Audio Configuration](#background-audio-configuration)
9
+ ### A shared player model
13
10
 
14
- ## MusicKit SwiftUI Integration
15
-
16
- ### Music Player Manager with `@Observable`
11
+ One `@Observable` object on the main actor owns access and subscription state
12
+ and drives the player. Views read it from the environment.
17
13
 
18
14
  ```swift
19
15
  import MusicKit
20
- import MediaPlayer
16
+ import Observation
21
17
 
22
- @Observable
23
18
  @MainActor
24
- final class MusicPlayerManager {
25
- let player = ApplicationMusicPlayer.shared
26
-
27
- var currentSong: Song?
28
- var isPlaying = false
29
- var playbackTime: TimeInterval = 0
30
- var queue: [Song] = []
31
- var hasSubscription = false
32
-
33
- func setup() async {
34
- // Check authorization
35
- let status = await MusicAuthorization.request()
36
- guard status == .authorized else { return }
37
-
38
- // Check subscription
39
- if let subscription = try? await MusicSubscription.current {
40
- hasSubscription = subscription.canPlayCatalogContent
41
- }
19
+ @Observable
20
+ final class ListeningSession {
21
+ private(set) var accessGranted = false
22
+ private(set) var catalogPlayable = false
23
+ private var membershipWatcher: Task<Void, Never>?
24
+
25
+ func prepare() async {
26
+ accessGranted = await MusicAuthorization.request() == .authorized
27
+ guard accessGranted else { return }
28
+
29
+ catalogPlayable = (try? await MusicSubscription.current.canPlayCatalogContent) ?? false
42
30
 
43
- // Observe subscription changes
44
- Task {
45
- for await subscription in MusicSubscription.subscriptionUpdates {
46
- hasSubscription = subscription.canPlayCatalogContent
31
+ membershipWatcher = Task { [weak self] in
32
+ let updates = MusicSubscription.subscriptionUpdates
33
+ for await membership in updates {
34
+ self?.catalogPlayable = membership.canPlayCatalogContent
47
35
  }
48
36
  }
49
37
  }
50
38
 
51
- func play(_ song: Song) async throws {
52
- guard hasSubscription else { return }
53
- player.queue = [song]
39
+ func play(_ track: Song) async throws {
40
+ guard catalogPlayable else { return }
41
+ let player = ApplicationMusicPlayer.shared
42
+ player.queue = [track]
54
43
  try await player.play()
55
- currentSong = song
56
- isPlaying = true
57
44
  }
58
45
 
59
- func togglePlayPause() {
60
- if player.state.playbackStatus == .playing {
61
- player.pause()
62
- isPlaying = false
63
- } else {
64
- Task {
65
- try? await player.play()
66
- isPlaying = true
67
- }
68
- }
46
+ func playOrPause() async throws {
47
+ let player = ApplicationMusicPlayer.shared
48
+ let isRunning = player.state.playbackStatus == .playing
49
+ if isRunning { player.pause() } else { try await player.play() }
69
50
  }
70
51
 
71
- func skip() async {
72
- try? await player.skipToNextEntry()
52
+ func next() async throws {
53
+ try await ApplicationMusicPlayer.shared.skipToNextEntry()
73
54
  }
74
55
  }
75
56
  ```
76
57
 
77
- ### SwiftUI Player View
58
+ Install it once near the root with `.environment(session)` and call
59
+ `prepare()` from a `.task`. The model does not keep the player in a property:
60
+ `ApplicationMusicPlayer` is not `Sendable`, and Swift 6 refuses an async call
61
+ such as `play()` on one reached through main-actor state. Each method reads
62
+ `ApplicationMusicPlayer.shared` into a local instead.
78
63
 
79
- ```swift
80
- import SwiftUI
81
- import MusicKit
64
+ ### Mini player bar
65
+
66
+ `ArtworkImage` renders MusicKit artwork at a requested size.
82
67
 
83
- struct MiniPlayerView: View {
84
- @Environment(MusicPlayerManager.self) private var manager
68
+ ```swift
69
+ struct NowPlayingBar: View {
70
+ @Environment(ListeningSession.self) private var session
71
+ let track: Song
85
72
 
86
73
  var body: some View {
87
- HStack {
88
- if let song = manager.currentSong {
89
- if let artwork = song.artwork {
90
- ArtworkImage(artwork, width: 44, height: 44)
91
- .clipShape(.rect(cornerRadius: 6))
92
- }
93
-
94
- VStack(alignment: .leading) {
95
- Text(song.title)
96
- .font(.subheadline)
97
- .lineLimit(1)
98
- Text(song.artistName)
99
- .font(.caption)
100
- .foregroundStyle(.secondary)
101
- }
102
-
103
- Spacer()
104
-
105
- Button {
106
- manager.togglePlayPause()
107
- } label: {
108
- Image(systemName: manager.isPlaying ? "pause.fill" : "play.fill")
109
- .font(.title2)
110
- }
111
-
112
- Button {
113
- Task { await manager.skip() }
114
- } label: {
115
- Image(systemName: "forward.fill")
116
- }
74
+ HStack(alignment: .center, spacing: 10) {
75
+ if let cover = track.artwork {
76
+ ArtworkImage(cover, width: 48, height: 48)
77
+ .clipShape(.rect(cornerRadius: 8))
117
78
  }
79
+ VStack(alignment: .leading, spacing: 2) {
80
+ Text(track.title).font(.subheadline.bold())
81
+ Text(track.artistName).foregroundStyle(.secondary).font(.caption)
82
+ }
83
+ Spacer(minLength: 8)
84
+ Button("Play or pause", systemImage: "playpause.fill") {
85
+ Task { try? await session.playOrPause() }
86
+ }
87
+ .labelStyle(.iconOnly)
118
88
  }
119
89
  .padding(.horizontal)
120
- .frame(height: 60)
121
90
  }
122
91
  }
123
92
  ```
124
93
 
125
- ### Search View
94
+ ### Search screen
95
+
96
+ Search as the text changes and clear the list when the field is empty.
126
97
 
127
98
  ```swift
128
- struct MusicSearchView: View {
129
- @State private var searchText = ""
130
- @State private var results: MusicItemCollection<Song> = []
131
- @Environment(MusicPlayerManager.self) private var manager
99
+ struct CatalogFinder: View {
100
+ @State private var phrase = ""
101
+ @State private var hits: MusicItemCollection<Song> = []
132
102
 
133
103
  var body: some View {
134
- NavigationStack {
135
- List(results) { song in
136
- Button {
137
- Task { try? await manager.play(song) }
138
- } label: {
139
- HStack {
140
- if let artwork = song.artwork {
141
- ArtworkImage(artwork, width: 50, height: 50)
142
- .clipShape(.rect(cornerRadius: 4))
143
- }
144
- VStack(alignment: .leading) {
145
- Text(song.title)
146
- Text(song.artistName)
147
- .font(.caption)
148
- .foregroundStyle(.secondary)
149
- }
150
- }
151
- }
152
- }
153
- .searchable(text: $searchText, prompt: "Search Apple Music")
154
- .onChange(of: searchText) {
155
- Task { await search() }
156
- }
157
- .navigationTitle("Search")
104
+ List(hits) { song in
105
+ LabeledContent(song.title, value: song.artistName)
106
+ }
107
+ .searchable(text: $phrase, prompt: "Artists, songs, albums")
108
+ .onChange(of: phrase) { _, newPhrase in
109
+ Task { await refresh(for: newPhrase) }
158
110
  }
159
111
  }
160
112
 
161
- private func search() async {
162
- guard !searchText.isEmpty else {
163
- results = []
164
- return
165
- }
166
- var request = MusicCatalogSearchRequest(term: searchText, types: [Song.self])
167
- request.limit = 25
168
- if let response = try? await request.response() {
169
- results = response.songs
170
- }
113
+ private func refresh(for text: String) async {
114
+ guard !text.isEmpty else { hits = []; return }
115
+ var search = MusicCatalogSearchRequest(term: text, types: [Song.self])
116
+ search.limit = 25
117
+ hits = (try? await search.response().songs) ?? []
171
118
  }
172
119
  }
173
120
  ```
174
121
 
175
- ## Genre and Chart Browsing
122
+ ## Genre and chart browsing
176
123
 
177
- ### Fetching Top Charts
124
+ Pass `genre` explicitly, even when you want all genres, so the intent is clear
125
+ at the call site.
178
126
 
179
127
  ```swift
180
- func fetchTopSongs() async throws -> MusicItemCollection<Song> {
181
- var request = MusicCatalogChartsRequest(
182
- genre: nil,
183
- kinds: [.mostPlayed],
184
- types: [Song.self]
185
- )
186
- request.limit = 50
187
- let response = try await request.response()
188
- return response.songCharts.first?.items ?? []
128
+ func topTracks() async throws -> MusicItemCollection<Song> {
129
+ let wanted: [MusicCatalogChartKind] = [.mostPlayed]
130
+ var charts = MusicCatalogChartsRequest(genre: nil, kinds: wanted, types: [Song.self])
131
+ charts.limit = 50
132
+ let result = try await charts.response()
133
+ guard let chart = result.songCharts.first else { return [] }
134
+ return chart.items
189
135
  }
190
136
  ```
191
137
 
192
- ### Fetching by Genre
138
+ A simple way to browse by genre is to search with the genre's name:
193
139
 
194
140
  ```swift
195
- func fetchSongsByGenre(_ genre: Genre) async throws -> MusicItemCollection<Song> {
196
- var request = MusicCatalogSearchRequest(term: genre.name, types: [Song.self])
197
- request.limit = 25
198
- let response = try await request.response()
199
- return response.songs
141
+ func tracks(tagged style: Genre) async throws -> MusicItemCollection<Song> {
142
+ let label = style.name
143
+ let search = MusicCatalogSearchRequest(term: label, types: [Song.self])
144
+ return try await search.response().songs
200
145
  }
201
146
  ```
202
147
 
203
- ## Library Management
204
-
205
- Library writes require MusicKit authorization and an enabled iCloud Music Library.
206
- Check `MusicSubscription.current.hasCloudLibraryEnabled` before adding items or
207
- modifying playlists.
148
+ ## Library management
208
149
 
209
- ### Adding to Library
150
+ Writing to the user's library needs two things: authorization, and iCloud Music
151
+ Library turned on for the account.
210
152
 
211
153
  ```swift
212
- func addToLibrary(_ song: Song) async throws {
213
- guard MusicAuthorization.currentStatus == .authorized else { return }
214
- let subscription = try await MusicSubscription.current
215
- guard subscription.hasCloudLibraryEnabled else { return }
216
-
217
- try await MusicLibrary.shared.add(song)
154
+ func canWriteLibrary() async -> Bool {
155
+ if MusicAuthorization.currentStatus != .authorized { return false }
156
+ let membership = try? await MusicSubscription.current
157
+ return membership?.hasCloudLibraryEnabled ?? false
218
158
  }
219
159
 
220
- func addAlbumToLibrary(_ album: Album) async throws {
221
- guard MusicAuthorization.currentStatus == .authorized else { return }
222
- let subscription = try await MusicSubscription.current
223
- guard subscription.hasCloudLibraryEnabled else { return }
160
+ func save(_ track: Song) async throws {
161
+ let library = MusicLibrary.shared
162
+ if await canWriteLibrary() { try await library.add(track) }
163
+ }
224
164
 
225
- try await MusicLibrary.shared.add(album)
165
+ func save(_ record: Album) async throws {
166
+ let library = MusicLibrary.shared
167
+ if await canWriteLibrary() { try await library.add(record) }
226
168
  }
227
169
  ```
228
170
 
229
- ### Fetching Library Content
171
+ Reading from the library uses `MusicLibraryRequest`:
230
172
 
231
173
  ```swift
232
- func fetchLibrarySongs() async throws -> MusicItemCollection<Song> {
233
- var request = MusicLibraryRequest<Song>()
234
- request.sort(by: \.lastPlayedDate, ascending: false)
235
- request.limit = 50
236
- let response = try await request.response()
237
- return response.items
174
+ func lastListened() async throws -> MusicItemCollection<Song> {
175
+ var query = MusicLibraryRequest<Song>()
176
+ query.sort(by: \.lastPlayedDate, ascending: false)
177
+ query.limit = 20
178
+ return try await query.response().items
238
179
  }
239
180
  ```
240
181
 
241
- ## Playlist Access
182
+ ## Playlist access
242
183
 
243
- ### Fetching User Playlists
184
+ A library request for playlists sorts only by `lastPlayedDate`,
185
+ `libraryAddedDate` or `name`; `Playlist.lastModifiedDate` exists but is not a
186
+ sort key.
244
187
 
245
188
  ```swift
246
- func fetchPlaylists() async throws -> MusicItemCollection<Playlist> {
247
- var request = MusicLibraryRequest<Playlist>()
248
- request.sort(by: \.lastModifiedDate, ascending: false)
249
- let response = try await request.response()
250
- return response.items
189
+ func recentPlaylists() async throws -> MusicItemCollection<Playlist> {
190
+ var query = MusicLibraryRequest<Playlist>()
191
+ query.sort(by: \.lastPlayedDate, ascending: false)
192
+ return try await query.response().items
251
193
  }
252
- ```
253
-
254
- ### Adding Tracks to a Playlist
255
194
 
256
- ```swift
257
- func addToPlaylist(_ playlist: Playlist, songs: [Song]) async throws {
258
- guard MusicAuthorization.currentStatus == .authorized else { return }
259
- let subscription = try await MusicSubscription.current
260
- guard subscription.hasCloudLibraryEnabled else { return }
261
-
262
- try await MusicLibrary.shared.add(songs, to: playlist)
195
+ func append(_ tracks: [Song], to list: Playlist) async throws {
196
+ guard await canWriteLibrary() else { return }
197
+ for track in tracks {
198
+ _ = try await MusicLibrary.shared.add(track, to: list)
199
+ }
263
200
  }
264
201
  ```
265
202
 
266
- ## Now Playing Session
203
+ ## Now Playing session
267
204
 
268
- Use `MPNowPlayingSession` when your app manages multiple simultaneous audio
269
- sessions (e.g., picture-in-picture video plus background music).
205
+ When an app runs more than one audio source at once (for example a
206
+ Picture in Picture video next to a music stream), give each its own
207
+ `MPNowPlayingSession`. Each session carries its own command center and info
208
+ center, and the app says which one should be the system's Now Playing entry.
270
209
 
271
210
  ```swift
272
- import MediaPlayer
273
211
  import AVFoundation
212
+ import MediaPlayer
274
213
 
275
- func createNowPlayingSession(for player: AVPlayer) -> MPNowPlayingSession {
276
- let session = MPNowPlayingSession(players: [player])
277
-
278
- // Session-scoped remote command center
279
- session.remoteCommandCenter.playCommand.addTarget { _ in
280
- player.play()
281
- return .success
282
- }
283
-
284
- session.remoteCommandCenter.pauseCommand.addTarget { _ in
285
- player.pause()
214
+ @MainActor
215
+ func makeVideoSession(for videoPlayer: AVPlayer) -> MPNowPlayingSession {
216
+ let clipSession = MPNowPlayingSession(players: [videoPlayer])
217
+ let controls = clipSession.remoteCommandCenter
218
+ controls.playCommand.addTarget { event in
219
+ videoPlayer.play()
286
220
  return .success
287
221
  }
288
-
289
- // Session-scoped now playing info
290
- session.nowPlayingInfoCenter.nowPlayingInfo = [
291
- MPMediaItemPropertyTitle: "Track Title",
292
- MPMediaItemPropertyArtist: "Artist Name"
222
+ clipSession.nowPlayingInfoCenter.nowPlayingInfo = [
223
+ MPMediaItemPropertyTitle: "Match highlights"
293
224
  ]
294
-
295
- // Activate this session to become the "now playing" app
296
- session.becomeActiveIfPossible { success in
297
- print("Now playing session active: \(success)")
225
+ clipSession.becomeActiveIfPossible { didBecomeActive in
226
+ // didBecomeActive is false if another app currently owns Now Playing.
298
227
  }
299
-
300
- return session
228
+ return clipSession
301
229
  }
302
230
  ```
303
231
 
304
- ## Background Audio Configuration
232
+ ## Background audio configuration
305
233
 
306
- ### Audio Session Setup
307
-
308
- Configure the audio session before starting playback to enable background audio.
234
+ Set up the audio session before playback starts:
309
235
 
310
236
  ```swift
311
- import AVFoundation
312
-
313
237
  func configureAudioSession() throws {
314
- let session = AVAudioSession.sharedInstance()
315
- try session.setCategory(.playback, mode: .default)
316
- try session.setActive(true)
238
+ let audio = AVAudioSession.sharedInstance()
239
+ try audio.setCategory(.playback, mode: .default)
240
+ try audio.setActive(true)
317
241
  }
318
242
  ```
319
243
 
320
- ### Info.plist Background Mode
244
+ Then list `audio` under the `UIBackgroundModes` key in Info.plist (in Xcode:
245
+ Signing and Capabilities, Background Modes, "Audio, AirPlay, and Picture in
246
+ Picture").
321
247
 
322
- Add `audio` to `UIBackgroundModes` in Info.plist:
248
+ ### Interruptions
323
249
 
324
- ```xml
325
- <key>UIBackgroundModes</key>
326
- <array>
327
- <string>audio</string>
328
- </array>
329
- ```
330
-
331
- ### Handling Interruptions
250
+ Calls, alarms and Siri interrupt playback. Pause when it starts; resume only if
251
+ the system says so when it ends. The async notification sequence keeps the
252
+ handler on the main actor.
332
253
 
333
254
  ```swift
334
- func observeInterruptions() {
335
- NotificationCenter.default.addObserver(
336
- forName: AVAudioSession.interruptionNotification,
337
- object: AVAudioSession.sharedInstance(),
338
- queue: .main
339
- ) { notification in
340
- guard let typeValue = notification.userInfo?[AVAudioSessionInterruptionTypeKey] as? UInt,
341
- let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
342
- return
343
- }
344
-
345
- switch type {
346
- case .began:
347
- // Pause UI, save state
348
- break
349
- case .ended:
350
- let options = notification.userInfo?[AVAudioSessionInterruptionOptionKey] as? UInt
351
- if let options, AVAudioSession.InterruptionOptions(rawValue: options).contains(.shouldResume) {
352
- // Resume playback
353
- }
354
- @unknown default:
355
- break
255
+ @MainActor
256
+ func followInterruptions(for engine: EpisodeEngine) async {
257
+ let feed = NotificationCenter.default.notifications(named: AVAudioSession.interruptionNotification)
258
+ for await note in feed {
259
+ let info = note.userInfo ?? [:]
260
+ guard let kindValue = info[AVAudioSessionInterruptionTypeKey] as? UInt,
261
+ let kind = AVAudioSession.InterruptionType(rawValue: kindValue) else { continue }
262
+
263
+ if kind == .began {
264
+ engine.suspend() // also remember the position
265
+ } else if kind == .ended {
266
+ let flags = AVAudioSession.InterruptionOptions(
267
+ rawValue: info[AVAudioSessionInterruptionOptionKey] as? UInt ?? 0)
268
+ if flags.contains(.shouldResume) { engine.resume() }
356
269
  }
357
270
  }
358
271
  }
359
272
  ```
360
273
 
361
- ### Route Change Handling
274
+ ### Route changes
362
275
 
363
- Pause playback when headphones are unplugged to avoid unexpected speaker output.
276
+ Pulling out headphones should stop the audio instead of moving it to the
277
+ speaker. The system reports that as `.oldDeviceUnavailable`.
364
278
 
365
279
  ```swift
366
- func observeRouteChanges() {
367
- NotificationCenter.default.addObserver(
368
- forName: AVAudioSession.routeChangeNotification,
369
- object: AVAudioSession.sharedInstance(),
370
- queue: .main
371
- ) { notification in
372
- guard let reasonValue = notification.userInfo?[AVAudioSessionRouteChangeReasonKey] as? UInt,
373
- let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else {
374
- return
375
- }
376
-
377
- if reason == .oldDeviceUnavailable {
378
- // Headphones were unplugged -- pause playback
379
- pausePlayback()
280
+ @MainActor
281
+ func followRouteChanges(for engine: EpisodeEngine) async {
282
+ let feed = NotificationCenter.default.notifications(named: AVAudioSession.routeChangeNotification)
283
+ for await note in feed {
284
+ let reasonValue = note.userInfo?[AVAudioSessionRouteChangeReasonKey] as? UInt ?? 0
285
+ if AVAudioSession.RouteChangeReason(rawValue: reasonValue) == .oldDeviceUnavailable {
286
+ engine.suspend()
380
287
  }
381
288
  }
382
289
  }
383
290
  ```
291
+
292
+ Start both from a long-lived `.task` or from the engine's own setup.
293
+ `EpisodeEngine` stands for whatever `@MainActor` type drives the app's custom
294
+ audio.