@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,500 +1,390 @@
1
1
  ---
2
2
  name: background-processing
3
- description: "Schedule and execute background work on iOS using BGTaskScheduler. Use when registering BGAppRefreshTask for short background fetches, BGProcessingTask for long-running maintenance, BGContinuedProcessingTask (iOS 26+) for foreground-started work that continues in background, background URLSession downloads, or background push notifications. Covers Info.plist configuration, expiration handling, task completion, and debugging with simulated launches."
3
+ description: "iOS background work with BGTaskScheduler: BGAppRefreshTask short fetches, BGProcessingTask long maintenance, BGContinuedProcessingTask (iOS 26+) for user-started work, background URLSession transfers, silent pushes, Info.plist identifiers and modes, handler registration, expiration and completion, simulated launches for debugging. Use when adding, reviewing or debugging background work. Not for visible notification UI."
4
4
  metadata:
5
- source: "dpearson2699/swift-ios-skills (PolyForm Perimeter 1.0.0)"
5
+ source: multi-agent-pipeline
6
6
  ---
7
+
7
8
  # Background Processing
8
9
 
9
- Register, schedule, and execute background work on iOS using the BackgroundTasks
10
- framework, background URLSession, and background push notifications.
10
+ iOS gives an app background time only on its own terms. You describe the work,
11
+ the system chooses the moment, or skips it entirely, and your code has to finish
12
+ or stop cleanly whenever it is told to. This skill covers the BackgroundTasks
13
+ framework, background `URLSession` transfers and silent pushes.
11
14
 
12
- ## Contents
15
+ Covers: background `URLSession` downloads and uploads. Not for designing
16
+ visible notification UI.
13
17
 
14
- - [Info.plist Configuration](#infoplist-configuration)
15
- - [BGTaskScheduler Registration](#bgtaskscheduler-registration)
16
- - [BGAppRefreshTask Patterns](#bgapprefreshtask-patterns)
17
- - [BGProcessingTask Patterns](#bgprocessingtask-patterns)
18
- - [BGContinuedProcessingTask (iOS 26+)](#bgcontinuedprocessingtask-ios-26)
19
- - [Background URLSession Downloads](#background-urlsession-downloads)
20
- - [Background Push Triggers](#background-push-triggers)
21
- - [Common Mistakes](#common-mistakes)
22
- - [Review Checklist](#review-checklist)
23
- - [References](#references)
18
+ ## Configuration
24
19
 
25
- ## Info.plist Configuration
20
+ **Permitted identifiers.** Every task identifier goes into the Info.plist
21
+ array `BGTaskSchedulerPermittedIdentifiers`. A missing entry makes
22
+ `submit(_:)` throw `BGTaskScheduler.Error.Code.notPermitted`. An entry may end
23
+ in a wildcard, such as `org.sample.photos.export.*`, for per-job identifiers.
26
24
 
27
- Every task identifier **must** be declared in `Info.plist` under
28
- `BGTaskSchedulerPermittedIdentifiers`, or `submit(_:)` throws
29
- `BGTaskScheduler.Error.Code.notPermitted`.
25
+ **Background modes.** `UIBackgroundModes` needs `fetch` for app refresh tasks
26
+ and `processing` for processing tasks. In Xcode: target, Signing &
27
+ Capabilities, Background Modes, then tick "Background fetch" and "Background
28
+ processing".
30
29
 
31
30
  ```xml
32
31
  <key>BGTaskSchedulerPermittedIdentifiers</key>
33
32
  <array>
34
- <string>com.example.app.refresh</string>
35
- <string>com.example.app.db-cleanup</string>
36
- <string>com.example.app.export.*</string>
33
+ <string>org.sample.photos.refresh</string>
34
+ <string>org.sample.photos.cleanup</string>
35
+ <string>org.sample.photos.export.*</string>
37
36
  </array>
38
- ```
39
-
40
- Also enable the required `UIBackgroundModes`:
41
-
42
- ```xml
43
37
  <key>UIBackgroundModes</key>
44
38
  <array>
45
- <string>fetch</string> <!-- Required for BGAppRefreshTask -->
46
- <string>processing</string> <!-- Required for BGProcessingTask -->
39
+ <string>fetch</string>
40
+ <string>processing</string>
47
41
  </array>
48
42
  ```
49
43
 
50
- In Xcode: target > Signing & Capabilities > Background Modes > enable "Background fetch" and "Background processing".
51
-
52
- ## BGTaskScheduler Registration
44
+ ## Registering handlers
53
45
 
54
- Register handlers **before** app launch completes. In UIKit, register in
55
- `application(_:didFinishLaunchingWithOptions:)`; in SwiftUI, register in `App.init()`.
56
-
57
- ### UIKit Registration
46
+ Refresh and processing handlers must be registered before the app finishes
47
+ launching: in `application(_:didFinishLaunchingWithOptions:)` for UIKit, or in
48
+ the `App` initializer for SwiftUI. Registering later, for instance from a
49
+ view's `.task`, is too late for the system to deliver a launch. Continued
50
+ processing tasks are the one exception, covered below. Register each
51
+ identifier once: a second registration of the same identifier kills the app.
58
52
 
59
53
  ```swift
60
54
  import BackgroundTasks
55
+ import SwiftUI
61
56
 
62
- @main
63
- class AppDelegate: UIResponder, UIApplicationDelegate {
64
- func application(
65
- _ application: UIApplication,
66
- didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
67
- ) -> Bool {
68
- BGTaskScheduler.shared.register(
69
- forTaskWithIdentifier: "com.example.app.refresh",
70
- using: nil // nil = default background queue
71
- ) { task in
72
- self.handleAppRefresh(task: task as! BGAppRefreshTask)
73
- }
74
-
75
- BGTaskScheduler.shared.register(
76
- forTaskWithIdentifier: "com.example.app.db-cleanup",
77
- using: nil
78
- ) { task in
79
- self.handleDatabaseCleanup(task: task as! BGProcessingTask)
80
- }
81
-
82
- return true
83
- }
57
+ enum TaskID {
58
+ static let refresh = "org.sample.photos.refresh"
59
+ static let cleanup = "org.sample.photos.cleanup"
84
60
  }
85
- ```
86
-
87
- ### SwiftUI Registration
88
-
89
- ```swift
90
- import SwiftUI
91
- import BackgroundTasks
92
61
 
93
62
  @main
94
- struct MyApp: App {
63
+ struct PhotosApp: App {
95
64
  init() {
96
- BGTaskScheduler.shared.register(
97
- forTaskWithIdentifier: "com.example.app.refresh",
98
- using: nil
99
- ) { task in
100
- BackgroundTaskManager.shared.handleAppRefresh(
101
- task: task as! BGAppRefreshTask
102
- )
65
+ BGTaskScheduler.shared.register(forTaskWithIdentifier: TaskID.refresh, using: nil) { task in
66
+ guard let refresh = task as? BGAppRefreshTask else { return }
67
+ LibrarySync.shared.handleRefresh(refresh)
68
+ }
69
+ BGTaskScheduler.shared.register(forTaskWithIdentifier: TaskID.cleanup, using: nil) { task in
70
+ guard let cleanup = task as? BGProcessingTask else { return }
71
+ LibrarySync.shared.handleCleanup(cleanup)
103
72
  }
104
73
  }
105
74
 
106
75
  var body: some Scene {
107
- WindowGroup { ContentView() }
76
+ WindowGroup {
77
+ LibraryView()
78
+ }
108
79
  }
109
80
  }
110
81
  ```
111
82
 
112
- ## BGAppRefreshTask Patterns
83
+ Passing `nil` for `using:` runs the handler on a default background queue.
84
+
85
+ ## App refresh tasks
113
86
 
114
- Short-lived tasks (~30 seconds) for fetching small data updates. The system
115
- decides when to launch based on usage patterns. Review notes should say
116
- `earliestBeginDate` is a lower-bound hint and the system may run the task later.
87
+ A refresh task gets roughly 30 seconds for a small update. The system picks
88
+ the moment based on how the user uses the app. `earliestBeginDate` only says
89
+ "not before"; the task may run much later or not at all, and reviews should
90
+ treat it that way.
117
91
 
118
92
  ```swift
119
- func scheduleAppRefresh() {
120
- let request = BGAppRefreshTaskRequest(
121
- identifier: "com.example.app.refresh"
122
- )
123
- request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
124
- // earliestBeginDate is a lower-bound hint; the system may delay launch.
125
- do {
126
- try BGTaskScheduler.shared.submit(request)
127
- } catch {
128
- print("Could not schedule app refresh: \(error)")
93
+ final class LibrarySync: Sendable {
94
+ static let shared = LibrarySync()
95
+
96
+ func scheduleRefresh() {
97
+ let request = BGAppRefreshTaskRequest(identifier: TaskID.refresh)
98
+ request.earliestBeginDate = .now.addingTimeInterval(15 * 60)
99
+ if (try? BGTaskScheduler.shared.submit(request)) == nil {
100
+ Log.background.error("Refresh request was rejected")
101
+ }
129
102
  }
130
- }
131
103
 
132
- func handleAppRefresh(task: BGAppRefreshTask) {
133
- // Schedule the next refresh before doing work
134
- scheduleAppRefresh()
135
-
136
- let fetchTask = Task {
137
- do {
138
- let data = try await APIClient.shared.fetchLatestFeed()
139
- await FeedStore.shared.update(with: data)
140
- task.setTaskCompleted(success: true)
141
- } catch {
142
- task.setTaskCompleted(success: false)
143
- }
104
+ func handleRefresh(_ task: BGAppRefreshTask) {
105
+ scheduleRefresh()
106
+ perform(task) { try await self.fetchNewAlbums() }
144
107
  }
108
+ }
145
109
 
146
- // CRITICAL: Handle expiration -- system can revoke time at any moment
147
- task.expirationHandler = {
148
- fetchTask.cancel()
149
- task.setTaskCompleted(success: false)
110
+ func perform(_ task: BGTask, _ job: @escaping @Sendable () async throws -> Void) {
111
+ nonisolated(unsafe) let task = task
112
+ let work = Task {
113
+ let finished = (try? await job()) != nil
114
+ task.setTaskCompleted(success: finished && !Task.isCancelled)
150
115
  }
116
+ task.expirationHandler = { work.cancel() }
151
117
  }
152
118
  ```
153
119
 
154
- ## BGProcessingTask Patterns
120
+ `Log.background` stands for an `os.Logger` owned by the app, and the
121
+ processing example below reuses `perform`. Two habits matter: schedule the
122
+ next run first, so a crash or expiry does not break the chain, and always
123
+ install an `expirationHandler`, because the system can take the time back at
124
+ any moment. The handler only cancels; the work `Task` is the one completion
125
+ path, so `setTaskCompleted(success:)` runs exactly once, with `false` after an
126
+ expiry. That relies on the job honouring cancellation (`URLSession` async calls
127
+ and `Task.checkCancellation()` throw), so the `Task` ends soon after the
128
+ handler fires.
155
129
 
156
- Long-running tasks (minutes) for maintenance, data processing, or cleanup.
157
- Runs only when device is idle and (optionally) charging. Review notes should say
158
- `earliestBeginDate` is a lower-bound hint and the system may run the task later.
130
+ `BGTask` is not `Sendable`, yet both the work `Task` and the expiration
131
+ handler need it, and the handler runs on a queue the framework picks. The
132
+ `nonisolated(unsafe)` local copy marks that sharing as deliberate; without it
133
+ Swift 6 rejects the `Task` closure with "passing closure as a 'sending'
134
+ parameter risks causing data races".
135
+
136
+ ## Processing tasks
137
+
138
+ Processing tasks can run for minutes and suit maintenance, indexing and
139
+ cleanup. They start only while the device is idle, and optionally only on
140
+ power.
159
141
 
160
142
  ```swift
161
- func scheduleProcessingTask() {
162
- let request = BGProcessingTaskRequest(
163
- identifier: "com.example.app.db-cleanup"
164
- )
143
+ func scheduleCleanup() {
144
+ let request = BGProcessingTaskRequest(identifier: TaskID.cleanup)
165
145
  request.requiresNetworkConnectivity = false
166
146
  request.requiresExternalPower = true
167
- request.earliestBeginDate = Date(timeIntervalSinceNow: 60 * 60)
168
- // earliestBeginDate is a lower-bound hint; the system may delay launch.
169
- do {
170
- try BGTaskScheduler.shared.submit(request)
171
- } catch {
172
- print("Could not schedule processing task: \(error)")
173
- }
147
+ request.earliestBeginDate = .now.addingTimeInterval(3600)
148
+ try? BGTaskScheduler.shared.submit(request)
174
149
  }
175
150
 
176
- func handleDatabaseCleanup(task: BGProcessingTask) {
177
- scheduleProcessingTask()
178
-
179
- let cleanupTask = Task {
180
- do {
181
- try await DatabaseManager.shared.purgeExpiredRecords()
182
- try await DatabaseManager.shared.rebuildIndexes()
183
- task.setTaskCompleted(success: true)
184
- } catch {
185
- task.setTaskCompleted(success: false)
186
- }
187
- }
188
-
189
- task.expirationHandler = {
190
- cleanupTask.cancel()
191
- task.setTaskCompleted(success: false)
192
- }
151
+ func handleCleanup(_ task: BGProcessingTask) {
152
+ scheduleCleanup()
153
+ perform(task) { try await purgeOrphanedThumbnails() }
193
154
  }
194
155
  ```
195
156
 
196
- ## BGContinuedProcessingTask (iOS 26+)
157
+ ## Continued processing tasks (iOS 26+)
197
158
 
198
- A task initiated in the foreground by a user action that continues running in the
199
- background. The system displays progress via a Live Activity. Conforms to
200
- `ProgressReporting`.
159
+ `BGContinuedProcessingTask` needs iOS or iPadOS 26.0 and is for work the user
160
+ starts on purpose, such as an export, that should keep going after they leave
161
+ the app. It differs from the other two types:
201
162
 
202
- **Availability:** iOS 26.0+, iPadOS 26.0+
203
-
204
- Unlike `BGAppRefreshTask` and `BGProcessingTask`, this task starts immediately
205
- from the foreground. The system can terminate it under resource pressure,
206
- prioritizing tasks that report minimal progress first. Set `expirationHandler` for user or system cancellation, cancel in-flight work, and clean up partial output before reporting completion.
163
+ - It starts right away from the foreground instead of waiting for the system.
164
+ - The system shows its progress to the user as a Live Activity; the task
165
+ adopts `ProgressReporting`.
166
+ - Under resource pressure the system may end it, and tasks reporting little
167
+ progress go first.
168
+ - The user or the system can cancel it; the `expirationHandler` must stop the
169
+ work and remove partial output, and the cancelled work then completes once.
170
+ - Its handler is exempt from the register-at-launch rule. `submit` expects
171
+ an identifier that already has a handler, so register the job's identifier
172
+ right before submitting it.
207
173
 
208
174
  ```swift
209
- import BackgroundTasks
210
-
211
- func startExport() {
212
- // Register the task handler at app launch, not here.
213
- // BGTaskScheduler requires registration before app launch completes.
214
- let jobID = UUID().uuidString
175
+ func startExport(of album: Album) {
176
+ let identifier = "org.sample.photos.export.\(UUID().uuidString)"
177
+ BGTaskScheduler.shared.register(forTaskWithIdentifier: identifier, using: nil) { task in
178
+ guard let export = task as? BGContinuedProcessingTask else { return }
179
+ handleExport(export)
180
+ }
215
181
  let request = BGContinuedProcessingTaskRequest(
216
- identifier: "com.example.app.export.\(jobID)",
217
- title: "Exporting Photos",
218
- subtitle: "Processing 247 items"
182
+ identifier: identifier,
183
+ title: "Exporting \(album.name)",
184
+ subtitle: "Preparing"
219
185
  )
220
- // Use a permitted base wildcard identifier: com.example.app.export.*
221
- // earliestBeginDate is ignored for continued processing requests.
222
- // .queue: begin as soon as possible if can't run immediately
223
- // .fail: fail submission if can't run immediately
224
- request.strategy = .queue
225
-
226
- do {
227
- try BGTaskScheduler.shared.submit(request)
228
- } catch {
229
- print("Could not submit continued processing task: \(error)")
186
+ request.strategy = .fail
187
+ if BGTaskScheduler.supportedResources.contains(.gpu) {
188
+ request.requiredResources = .gpu
189
+ }
190
+ guard (try? BGTaskScheduler.shared.submit(request)) != nil else {
191
+ showExportUnavailable()
192
+ return
230
193
  }
231
194
  }
232
195
 
233
- func performExport(task: BGContinuedProcessingTask) async {
234
- let items = await PhotoLibrary.shared.itemsToExport()
235
- let progress = task.progress
236
- progress.totalUnitCount = Int64(items.count)
237
-
238
- for (index, item) in items.enumerated() {
239
- if Task.isCancelled { break }
240
-
241
- await PhotoExporter.shared.export(item)
242
- progress.completedUnitCount = Int64(index + 1)
243
-
244
- // Update the user-facing title/subtitle
245
- task.updateTitle(
246
- "Exporting Photos",
247
- subtitle: "\(index + 1) of \(items.count) complete"
248
- )
196
+ func handleExport(_ task: BGContinuedProcessingTask) {
197
+ nonisolated(unsafe) let task = task
198
+ let work = Task { await runExport(task, items: ExportQueue.shared.pending) }
199
+ task.expirationHandler = {
200
+ work.cancel()
201
+ ExportQueue.shared.removePartialFiles()
249
202
  }
203
+ }
250
204
 
205
+ func runExport(_ task: BGContinuedProcessingTask, items: [PhotoAsset]) async {
206
+ task.progress.totalUnitCount = Int64(items.count)
207
+ var done: Int64 = 0
208
+ for asset in items where !Task.isCancelled {
209
+ await render(asset)
210
+ done += 1
211
+ task.progress.completedUnitCount = done
212
+ task.updateTitle(task.title, subtitle: "Photo \(done) of \(items.count)")
213
+ }
251
214
  task.setTaskCompleted(success: !Task.isCancelled)
252
215
  }
253
216
  ```
254
217
 
255
- For GPU work, check support and enable Background GPU Access (`com.apple.developer.background-tasks.continued-processing.gpu`):
218
+ When the task expires, the cancelled `Task` leaves the loop and
219
+ `runExport` reports `success: false`, so completion still happens exactly once. The
220
+ `nonisolated(unsafe)` copy in `handleExport` is there for the same reason as in
221
+ `perform`.
256
222
 
257
- ```swift
258
- let supported = BGTaskScheduler.supportedResources
259
- if supported.contains(.gpu) {
260
- request.requiredResources = .gpu
261
- }
262
- ```
223
+ - Each job gets its own identifier under a permitted wildcard base, which
224
+ also keeps every registration unique.
225
+ - `earliestBeginDate` is ignored for this request type.
226
+ - `strategy` is `.queue` (start as soon as allowed) or `.fail` (reject the
227
+ submission if it cannot start now).
228
+ - GPU work needs both the `supportedResources` check and the entitlement for
229
+ background GPU use (key ending in `continued-processing.gpu`).
263
230
 
264
- ## Background URLSession Downloads
231
+ Strategy choice, cancellation and progress ranking are expanded in the
232
+ [patterns reference](references/background-task-patterns.md).
265
233
 
266
- Use `URLSessionConfiguration.background` for downloads that continue even after
267
- the app is suspended or terminated. The system handles the transfer out of
268
- process.
234
+ ## Background URLSession
235
+
236
+ A session built with `URLSessionConfiguration.background(withIdentifier:)`
237
+ hands transfers to a system daemon, so they keep running while the app is
238
+ suspended or even terminated.
269
239
 
270
240
  ```swift
271
- class DownloadManager: NSObject, URLSessionDownloadDelegate {
272
- static let shared = DownloadManager()
241
+ @MainActor
242
+ final class EpisodeDownloader: NSObject, URLSessionDownloadDelegate {
243
+ static let shared = EpisodeDownloader()
244
+
245
+ var finishEvents: (() -> Void)?
273
246
 
274
- private lazy var session: URLSession = {
247
+ lazy var session: URLSession = {
275
248
  let config = URLSessionConfiguration.background(
276
- withIdentifier: "com.example.app.background-download"
249
+ withIdentifier: "org.sample.podcasts.downloads"
277
250
  )
278
- config.isDiscretionary = true
279
251
  config.sessionSendsLaunchEvents = true
280
- return URLSession(configuration: config, delegate: self, delegateQueue: nil)
252
+ config.isDiscretionary = true
253
+ return URLSession(
254
+ configuration: config,
255
+ delegate: self,
256
+ delegateQueue: nil
257
+ )
281
258
  }()
282
259
 
283
- func startDownload(from url: URL) {
284
- let task = session.downloadTask(with: url)
285
- task.earliestBeginDate = Date(timeIntervalSinceNow: 60)
286
- task.resume()
260
+ func fetch(_ remote: URL, notBefore date: Date? = nil) {
261
+ let job = session.downloadTask(with: remote)
262
+ job.earliestBeginDate = date
263
+ job.resume()
287
264
  }
288
265
 
289
- func urlSession(
290
- _ session: URLSession,
291
- downloadTask: URLSessionDownloadTask,
292
- didFinishDownloadingTo location: URL
293
- ) {
294
- // Move file from tmp before this method returns
295
- let dest = FileManager.default.urls(
296
- for: .documentDirectory, in: .userDomainMask
297
- )[0].appendingPathComponent("download.dat")
298
- try? FileManager.default.moveItem(at: location, to: dest)
266
+ nonisolated func urlSession(_ s: URLSession, downloadTask job: URLSessionDownloadTask, didFinishDownloadingTo tmp: URL) {
267
+ let name = job.response?.suggestedFilename ?? "episode-\(job.taskIdentifier)"
268
+ let target = URL.documentsDirectory.appending(path: name)
269
+ try? FileManager.default.moveItem(at: tmp, to: target)
299
270
  }
300
271
 
301
- func urlSession(
302
- _ session: URLSession,
303
- task: URLSessionTask,
304
- didCompleteWithError error: (any Error)?
305
- ) {
306
- if let error { print("Download failed: \(error)") }
272
+ nonisolated func urlSession(_ s: URLSession, task job: URLSessionTask, didCompleteWithError error: (any Error)?) {
273
+ guard let error else { return }
274
+ Log.background.error("Transfer failed: \(error)")
307
275
  }
308
- }
309
- ```
310
-
311
- Handle app relaunch - store and invoke the system completion handler:
312
-
313
- ```swift
314
- // In AppDelegate:
315
- func application(
316
- _ application: UIApplication,
317
- handleEventsForBackgroundURLSession identifier: String,
318
- completionHandler: @escaping () -> Void
319
- ) {
320
- backgroundSessionCompletionHandler = completionHandler
321
- }
322
-
323
- // In URLSessionDelegate - call stored handler when events finish:
324
- func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
325
- Task { @MainActor in
326
- self.backgroundSessionCompletionHandler?()
327
- self.backgroundSessionCompletionHandler = nil
328
- }
329
- }
330
- ```
331
-
332
- ## Background Push Triggers
333
-
334
- Silent push notifications wake your app briefly to fetch new content. Set
335
- `content-available: 1` in the push payload.
336
-
337
- ```json
338
- { "aps": { "content-available": 1 }, "custom-data": "new-messages" }
339
- ```
340
-
341
- Send the APNs request with `apns-push-type: background` and
342
- `apns-priority: 5`. Background push delivery is low priority and not
343
- guaranteed; keep sends infrequent, generally no more than two or three per
344
- hour.
345
-
346
- Handle in AppDelegate:
347
276
 
348
- ```swift
349
- func application(
350
- _ application: UIApplication,
351
- didReceiveRemoteNotification userInfo: [AnyHashable: Any],
352
- fetchCompletionHandler completionHandler:
353
- @escaping (UIBackgroundFetchResult) -> Void
354
- ) {
355
- Task {
356
- do {
357
- let hasNew = try await MessageStore.shared.fetchNewMessages()
358
- completionHandler(hasNew ? .newData : .noData)
359
- } catch {
360
- completionHandler(.failed)
277
+ nonisolated func urlSessionDidFinishEvents(forBackgroundURLSession s: URLSession) {
278
+ Task { @MainActor in
279
+ self.finishEvents?()
280
+ self.finishEvents = nil
361
281
  }
362
282
  }
363
283
  }
364
284
  ```
365
285
 
366
- Enable "Remote notifications" in Background Modes and register:
367
-
368
- ```swift
369
- UIApplication.shared.registerForRemoteNotifications()
370
- ```
371
-
372
- ## Common Mistakes
373
-
374
- ### 1. Missing Info.plist identifiers
375
-
376
- ```swift
377
- // DON'T: Submit a task whose identifier isn't in BGTaskSchedulerPermittedIdentifiers
378
- let request = BGAppRefreshTaskRequest(identifier: "com.example.app.refresh")
379
- try BGTaskScheduler.shared.submit(request) // Throws .notPermitted
380
-
381
- // DO: Add every identifier to Info.plist BGTaskSchedulerPermittedIdentifiers
382
- // <string>com.example.app.refresh</string>
383
- ```
286
+ - The class is `@MainActor`, so the lazy session and the stored handler are
287
+ safe to touch from UIKit callbacks. The session calls its delegate on its
288
+ own queue, hence the `nonisolated` delegate methods.
289
+ - `isDiscretionary` lets the system wait for good conditions. With
290
+ `sessionSendsLaunchEvents` on, finished transfers launch the app again.
291
+ - A delegate is required (`delegateQueue: nil` is fine). The async and
292
+ completion-handler task APIs do not work with background sessions.
293
+ - The temporary file disappears when `didFinishDownloadingTo` returns, so move
294
+ it inside that method.
295
+ - Failures arrive in `urlSession(_:task:didCompleteWithError:)`.
384
296
 
385
- ### 2. Not calling setTaskCompleted(success:)
297
+ When the system relaunches the app for session events, keep the handler it
298
+ gives you and call it once the delegate reports the events are done:
386
299
 
387
300
  ```swift
388
- // DON'T: Return without marking completion -- system penalizes future scheduling
389
- func handleRefresh(task: BGAppRefreshTask) {
390
- Task {
391
- let data = try await fetchData()
392
- await store.update(data)
393
- // Missing: task.setTaskCompleted(success:)
394
- }
395
- }
396
-
397
- // DO: Always call setTaskCompleted on every code path
398
- func handleRefresh(task: BGAppRefreshTask) {
399
- let work = Task {
400
- do {
401
- let data = try await fetchData()
402
- await store.update(data)
403
- task.setTaskCompleted(success: true)
404
- } catch {
405
- task.setTaskCompleted(success: false)
406
- }
407
- }
408
- task.expirationHandler = {
409
- work.cancel()
410
- task.setTaskCompleted(success: false)
411
- }
301
+ func application(_ app: UIApplication,
302
+ handleEventsForBackgroundURLSession id: String,
303
+ completionHandler done: @escaping () -> Void) {
304
+ EpisodeDownloader.shared.finishEvents = done
305
+ _ = EpisodeDownloader.shared.session
412
306
  }
413
307
  ```
414
308
 
415
- ### 3. Ignoring the expiration handler
309
+ ## Background pushes
416
310
 
417
- ```swift
418
- // DON'T: Assume your task will run to completion
419
- func handleCleanup(task: BGProcessingTask) {
420
- Task { await heavyWork() }
421
- // No expirationHandler -- system terminates ungracefully
422
- }
311
+ A silent push wakes the app for a short time. The payload carries
312
+ `content-available: 1` plus your own keys. On the APNs request, set the
313
+ push type header to `background` and the priority header to `5`.
423
314
 
424
- // DO: Set expirationHandler to cancel work and mark completed
425
- func handleCleanup(task: BGProcessingTask) {
426
- let work = Task { await heavyWork() }
427
- task.expirationHandler = {
428
- work.cancel()
429
- task.setTaskCompleted(success: false)
430
- }
315
+ ```json
316
+ {
317
+ "aps": { "content-available": 1 },
318
+ "album-id": "A41"
431
319
  }
432
320
  ```
433
321
 
434
- ### 4. Scheduling too frequently
322
+ Delivery is low priority and never guaranteed. Keep it to two or three per hour
323
+ at most. Enable Remote notifications under Background Modes and call
324
+ `UIApplication.shared.registerForRemoteNotifications()`.
435
325
 
436
326
  ```swift
437
- // DON'T: Request refresh every minute -- system throttles aggressively
438
- request.earliestBeginDate = Date(timeIntervalSinceNow: 60)
439
-
440
- // DO: Use reasonable intervals (15+ minutes for refresh)
441
- request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
442
- // earliestBeginDate is a hint -- the system chooses actual launch time
443
- ```
444
-
445
- ### 5. Over-relying on background time
446
-
447
- ```swift
448
- // DON'T: Start a 10-minute operation assuming it will finish
449
- func handleRefresh(task: BGAppRefreshTask) {
450
- Task { await tenMinuteSync() }
451
- }
452
-
453
- // DO: Design work to be incremental and cancellable
454
- func handleRefresh(task: BGAppRefreshTask) {
455
- let work = Task {
456
- for batch in batches {
457
- try Task.checkCancellation()
458
- await processBatch(batch)
459
- await saveBatchProgress(batch)
460
- }
461
- task.setTaskCompleted(success: true)
327
+ func application(_ app: UIApplication,
328
+ didReceiveRemoteNotification payload: [AnyHashable: Any],
329
+ fetchCompletionHandler reply: @escaping (UIBackgroundFetchResult) -> Void) {
330
+ guard let albumID = payload["album-id"] as? String else {
331
+ return reply(.noData)
462
332
  }
463
- task.expirationHandler = {
464
- work.cancel()
465
- task.setTaskCompleted(success: false)
333
+ Task {
334
+ let outcome: Result<Bool, any Error> = await LibrarySync.shared.refreshAlbum(albumID)
335
+ switch outcome {
336
+ case .success(true): reply(.newData)
337
+ case .success(false): reply(.noData)
338
+ case .failure: reply(.failed)
339
+ }
466
340
  }
467
341
  }
468
342
  ```
469
343
 
470
- ## Review Checklist
471
-
472
- - [ ] All task identifiers listed in `BGTaskSchedulerPermittedIdentifiers`
473
- - [ ] Required `UIBackgroundModes` enabled (`fetch`, `processing`)
474
- - [ ] Tasks registered before app launch completes
475
- - [ ] `setTaskCompleted(success:)` called on every code path
476
- - [ ] `expirationHandler` set and cancels in-flight work
477
- - [ ] Next task scheduled inside the handler (re-schedule pattern)
478
- - [ ] `earliestBeginDate` uses reasonable intervals and is treated as a hint
479
- - [ ] Background URLSession uses delegate (not async/closures)
480
- - [ ] Background URLSession file moved in `didFinishDownloadingTo` before return
481
- - [ ] `handleEventsForBackgroundURLSession` stores and calls completion handler
482
- - [ ] Background push payload includes `content-available: 1`
483
- - [ ] Background push APNs request uses `apns-push-type: background` and `apns-priority: 5`
484
- - [ ] `fetchCompletionHandler` called promptly with correct result
485
- - [ ] BGContinuedProcessingTask reports progress via `ProgressReporting`
486
- - [ ] Work is incremental and cancellation-safe (`Task.checkCancellation()`)
487
- - [ ] No blocking synchronous work in task handlers
344
+ ## Common mistakes
345
+
346
+ - **Identifier missing from Info.plist.** `submit` throws `.notPermitted`.
347
+ - **Skipping `setTaskCompleted(success:)`.** The system schedules the app less
348
+ often afterwards. Complete on every path, errors included.
349
+ - **No expiration handler.** The task is killed abruptly instead of stopping
350
+ cleanly. Cancel the work from the handler and let the cancelled work report
351
+ `success: false`, so completion happens once.
352
+ - **Asking too often.** A refresh every minute gets throttled hard. Use 15
353
+ minutes or more, and remember `earliestBeginDate` is a hint.
354
+ - **Too much work per run.** A ten-minute job inside a refresh task will not
355
+ finish. Split work into small cancellable batches, call
356
+ `try Task.checkCancellation()` between them and save progress.
357
+
358
+ ## Review checklist
359
+
360
+ - [ ] Every identifier is in `BGTaskSchedulerPermittedIdentifiers`
361
+ - [ ] `UIBackgroundModes` has `fetch` and/or `processing` as needed
362
+ - [ ] Refresh and processing handlers are registered before launch completes;
363
+ continued processing handlers are registered before their `submit`
364
+ - [ ] `setTaskCompleted(success:)` runs exactly once on every path
365
+ - [ ] `expirationHandler` cancels in-flight work
366
+ - [ ] The handler schedules the next task
367
+ - [ ] `earliestBeginDate` is sensible and read as a lower bound only
368
+ - [ ] Background `URLSession` uses a delegate, not async or closure APIs
369
+ - [ ] Downloaded files are moved inside `didFinishDownloadingTo`
370
+ - [ ] `handleEventsForBackgroundURLSession` stores its handler and it is called later
371
+ - [ ] Silent push payload has `content-available: 1`
372
+ - [ ] APNs headers: push type `background`, priority `5`
373
+ - [ ] `fetchCompletionHandler` is called promptly with the right result
374
+ - [ ] Continued processing tasks report progress through `ProgressReporting`
375
+ - [ ] Work is incremental and checks cancellation (`Task.checkCancellation()`)
376
+ - [ ] Handlers never block on synchronous work
488
377
 
489
378
  ## References
490
379
 
491
- - See [references/background-task-patterns.md](references/background-task-patterns.md) for extended patterns, background
492
- URLSession edge cases, debugging with simulated launches, and background push
493
- best practices.
494
- - [BGTaskScheduler](https://sosumi.ai/documentation/backgroundtasks/bgtaskscheduler)
495
- - [BGAppRefreshTask](https://sosumi.ai/documentation/backgroundtasks/bgapprefreshtask)
496
- - [BGProcessingTask](https://sosumi.ai/documentation/backgroundtasks/bgprocessingtask)
497
- - [BGContinuedProcessingTask](https://sosumi.ai/documentation/backgroundtasks/bgcontinuedprocessingtask) (iOS 26+)
498
- - [BGContinuedProcessingTaskRequest](https://sosumi.ai/documentation/backgroundtasks/bgcontinuedprocessingtaskrequest) (iOS 26+)
499
- - [Using background tasks to update your app](https://sosumi.ai/documentation/uikit/using-background-tasks-to-update-your-app)
500
- - [Performing long-running tasks on iOS and iPadOS](https://sosumi.ai/documentation/backgroundtasks/performing-long-running-tasks-on-ios-and-ipados)
380
+ - [Background task patterns](references/background-task-patterns.md):
381
+ debugging, error codes, checkpointing, URLSession extras, push limits,
382
+ SwiftUI `.backgroundTask`, continued-processing details
383
+ - Apple documentation:
384
+ [scheduler](https://developer.apple.com/documentation/backgroundtasks/bgtaskscheduler),
385
+ [refresh task](https://developer.apple.com/documentation/backgroundtasks/bgapprefreshtask),
386
+ [processing task](https://developer.apple.com/documentation/backgroundtasks/bgprocessingtask),
387
+ [continued processing task](https://developer.apple.com/documentation/backgroundtasks/bgcontinuedprocessingtask) (iOS 26+),
388
+ [continued processing request](https://developer.apple.com/documentation/backgroundtasks/bgcontinuedprocessingtaskrequest) (iOS 26+),
389
+ [keeping content fresh with background tasks](https://developer.apple.com/documentation/uikit/using-background-tasks-to-update-your-app),
390
+ [long-running work](https://developer.apple.com/documentation/backgroundtasks/performing-long-running-tasks-on-ios-and-ipados)