@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,490 +1,414 @@
1
1
  ---
2
2
  name: ios-networking
3
- description: "Build, review, or improve networking code in iOS/macOS apps using URLSession with async/await, structured concurrency, and modern Swift patterns. Use when working with REST APIs, downloading files, uploading data, WebSocket connections, pagination, retry logic, request middleware, caching, background transfers, or network reachability monitoring. Also use when handling HTTP requests, API clients, network error handling, or data fetching in Swift apps."
3
+ description: "URLSession with async/await and structured concurrency on iOS and macOS: HTTP requests, REST API clients, response validation, error handling, retry with backoff, middleware and token refresh, pagination, caching, downloads, uploads, background transfers, WebSockets, NWPathMonitor reachability, Network.framework, ATS. Use when building or reviewing networking code, API clients or data fetching. Not for pinning (swift-security) or Keychain internals."
4
4
  metadata:
5
- source: "dpearson2699/swift-ios-skills (PolyForm Perimeter 1.0.0)"
5
+ source: multi-agent-pipeline
6
6
  ---
7
7
 
8
8
  # iOS Networking
9
9
 
10
- Modern networking patterns for iOS 26+ using URLSession with async/await and
11
- structured concurrency. All examples target Swift 6.3. No third-party
12
- dependencies required -- URLSession covers the vast majority of networking
13
- needs.
14
-
15
- ## Contents
16
-
17
- - [Core URLSession async/await](#core-urlsession-asyncawait)
18
- - [API Client Architecture](#api-client-architecture)
19
- - [Error Handling](#error-handling)
20
- - [Pagination](#pagination)
21
- - [Network Reachability](#network-reachability)
22
- - [Configuring URLSession](#configuring-urlsession)
23
- - [App Transport Security (ATS)](#app-transport-security-ats)
24
- - [Common Mistakes](#common-mistakes)
25
- - [Review Checklist](#review-checklist)
26
- - [References](#references)
10
+ URLSession handles nearly every networking job an app has, and it needs no
11
+ third-party package to do it well. Baseline for this skill: iOS 26, Swift 6.3.
27
12
 
28
13
  ## Core URLSession async/await
29
14
 
30
- URLSession gained native async/await overloads in iOS 15. Prefer these for
31
- foreground data, upload, download, and streaming work. Background URLSession
32
- transfers are the main exception: they still use task/delegate APIs so the
33
- system can deliver events after suspension or relaunch.
15
+ The async overloads (iOS 15 and later) are the default for foreground work:
16
+ data, upload, download and streaming. The one exception is a background
17
+ session. The system delivers its events after the app was suspended or even
18
+ relaunched, so it keeps the task-plus-delegate API.
34
19
 
35
- ### Data Requests
20
+ ### Data requests
36
21
 
37
22
  ```swift
38
- // Basic GET
39
- let (data, response) = try await URLSession.shared.data(from: url)
40
-
41
- // With a configured URLRequest
42
- var request = URLRequest(url: url)
43
- request.httpMethod = "POST"
44
- request.setValue("application/json", forHTTPHeaderField: "Content-Type")
45
- request.httpBody = try JSONEncoder().encode(payload)
46
- request.timeoutInterval = 30
47
- request.cachePolicy = .reloadIgnoringLocalCacheData
48
-
49
- let (data, response) = try await URLSession.shared.data(for: request)
23
+ let (forecast, _) = try await URLSession.shared.data(from: forecastURL)
24
+
25
+ var order = URLRequest(url: ordersURL)
26
+ order.httpMethod = "POST"
27
+ order.addValue("application/json", forHTTPHeaderField: "Content-Type")
28
+ order.httpBody = try JSONEncoder().encode(newOrder)
29
+ order.timeoutInterval = 30
30
+ order.cachePolicy = .reloadIgnoringLocalCacheData
31
+ let (confirmation, reply) = try await session.data(for: order)
50
32
  ```
51
33
 
52
- ### Response Validation
34
+ ### Response validation
53
35
 
54
- Always validate the HTTP status code before decoding. URLSession does not
55
- throw for 4xx/5xx responses -- it only throws for transport-level failures.
36
+ A 404 or a 503 is not a thrown error. URLSession throws only when the
37
+ transport fails (no route, TLS failure, cancellation), so check the status
38
+ yourself before touching the body:
56
39
 
57
40
  ```swift
58
- guard let httpResponse = response as? HTTPURLResponse else {
41
+ guard let status = (response as? HTTPURLResponse)?.statusCode else {
59
42
  throw NetworkError.invalidResponse
60
43
  }
61
-
62
- guard (200..<300).contains(httpResponse.statusCode) else {
63
- throw NetworkError.httpError(
64
- statusCode: httpResponse.statusCode,
65
- data: data
66
- )
44
+ if !(200...299).contains(status) {
45
+ throw NetworkError.httpStatus(code: status, body: data, message: nil)
67
46
  }
68
47
  ```
69
48
 
70
- ### JSON Decoding with Codable
49
+ ### Decoding with Codable
71
50
 
72
51
  ```swift
73
- func fetch<T: Decodable>(_ type: T.Type, from url: URL) async throws -> T {
74
- let (data, response) = try await URLSession.shared.data(from: url)
75
-
76
- guard let httpResponse = response as? HTTPURLResponse,
77
- (200..<300).contains(httpResponse.statusCode) else {
78
- throw NetworkError.invalidResponse
79
- }
80
-
52
+ func load<Model: Decodable>(_ kind: Model.Type, at address: URL) async throws -> Model {
53
+ let (bytes, reply) = try await session.data(from: address)
54
+ try validate(reply, body: bytes)
81
55
  let decoder = JSONDecoder()
82
56
  decoder.dateDecodingStrategy = .iso8601
83
57
  decoder.keyDecodingStrategy = .convertFromSnakeCase
84
- return try decoder.decode(T.self, from: data)
58
+ return try decoder.decode(kind, from: bytes)
85
59
  }
86
60
  ```
87
61
 
88
- ### Downloads and Uploads
62
+ ### Downloads and uploads
89
63
 
90
- Use `download(for:)` for large files -- it streams to disk instead of
91
- loading the entire payload into memory.
64
+ `download(for:)` streams the body to a file instead of memory, so use it for
65
+ anything large. The returned file is temporary: move or copy it right away.
92
66
 
93
67
  ```swift
94
- // Download to a temporary file
95
- let (localURL, response) = try await URLSession.shared.download(for: request)
68
+ let (tempFile, _) = try await session.download(for: manualRequest)
69
+ let target = URL.documentsDirectory.appending(path: "manual.pdf")
70
+ try FileManager.default.moveItem(at: tempFile, to: target)
96
71
 
97
- // Move or copy the returned temporary file promptly.
98
- let destination = documentsDirectory.appendingPathComponent("file.zip")
99
- try FileManager.default.moveItem(at: localURL, to: destination)
72
+ let (ack, _) = try await session.upload(for: syncRequest, from: jsonData)
73
+ let (receipt, _) = try await session.upload(for: videoRequest, fromFile: videoURL)
100
74
  ```
101
75
 
102
- For delegate-based `URLSessionDownloadDelegate`, move or open the temporary
103
- file before `urlSession(_:downloadTask:didFinishDownloadingTo:)` returns.
104
-
105
- Background sessions are delegate-driven transfer queues. Use task creation
106
- APIs such as `downloadTask(with:)` and file-backed `uploadTask(with:fromFile:)`,
107
- then handle `URLSessionDelegate` / task delegate callbacks. Do not use async
108
- convenience APIs such as `data(for:)`, `download(for:)`, or `upload(for:)` as
109
- the durable background-session pattern.
110
-
111
- ```swift
112
- // Upload data
113
- let (data, response) = try await URLSession.shared.upload(for: request, from: bodyData)
76
+ A `URLSessionDownloadDelegate` gets the same kind of temporary file and has
77
+ to relocate or open it before `urlSession(_:downloadTask:didFinishDownloadingTo:)` returns.
114
78
 
115
- // Upload from file
116
- let (data, response) = try await URLSession.shared.upload(for: request, fromFile: fileURL)
117
- ```
79
+ A background session is a delegate-driven transfer queue. Its work is
80
+ `downloadTask(with:)` plus `uploadTask(with:fromFile:)` for uploads from disk,
81
+ reported through session and task delegate callbacks. The async conveniences
82
+ `data(for:)`, `download(for:)` and `upload(for:)` are not a durable
83
+ background pattern. See [background and WebSocket guide](references/background-websocket.md).
118
84
 
119
85
  ### Streaming with AsyncBytes
120
86
 
121
- Use `bytes(for:)` for streaming responses, progress tracking, or
122
- line-delimited data (e.g., server-sent events).
87
+ `bytes(for:)` hands you the body as it arrives: useful for progress, for
88
+ newline-delimited JSON and for server-sent events.
123
89
 
124
90
  ```swift
125
- let (bytes, response) = try await URLSession.shared.bytes(for: request)
126
-
127
- for try await line in bytes.lines {
128
- // Process each line as it arrives (e.g., SSE stream)
129
- handleEvent(line)
91
+ let (stream, _) = try await session.bytes(for: request)
92
+ for try await line in stream.lines {
93
+ handle(line)
130
94
  }
131
95
  ```
132
96
 
133
- ## API Client Architecture
97
+ ## API client architecture
134
98
 
135
- ### Protocol-Based Client
99
+ ### Protocol-based client
136
100
 
137
- Define a protocol for testability. This lets you swap implementations in
138
- tests without mocking URLSession directly.
101
+ Put the client behind a protocol. Tests then substitute a double instead of
102
+ mocking URLSession.
139
103
 
140
104
  ```swift
141
105
  protocol APIClientProtocol: Sendable {
142
- func fetch<T: Decodable & Sendable>(
143
- _ type: T.Type,
144
- endpoint: Endpoint
145
- ) async throws -> T
146
-
147
- func send<T: Decodable & Sendable>(
148
- _ type: T.Type,
149
- endpoint: Endpoint,
150
- body: some Encodable & Sendable
151
- ) async throws -> T
106
+ func fetch<Reply: Decodable & Sendable>(_ endpoint: Endpoint, as kind: Reply.Type) async throws -> Reply
107
+ func send<Reply: Decodable & Sendable>(_ endpoint: Endpoint, json payload: some Encodable & Sendable,
108
+ as kind: Reply.Type) async throws -> Reply
109
+ func perform(_ endpoint: Endpoint) async throws
110
+ func upload<Reply: Decodable & Sendable>(_ endpoint: Endpoint, bytes: Data,
111
+ as kind: Reply.Type) async throws -> Reply
152
112
  }
153
- ```
154
113
 
155
- ```swift
156
114
  struct Endpoint: Sendable {
157
- let path: String
158
- var method: String = "GET"
115
+ enum HTTPMethod: String, Sendable { case get, post, put, patch, delete }
116
+
117
+ var path: String
118
+ var method: HTTPMethod = .get
159
119
  var queryItems: [URLQueryItem] = []
160
120
  var headers: [String: String] = [:]
161
-
162
- func url(relativeTo baseURL: URL) -> URL {
163
- guard let components = URLComponents(
164
- url: baseURL.appendingPathComponent(path),
165
- resolvingAgainstBaseURL: true
166
- ) else {
167
- preconditionFailure("Invalid URL components for path: \(path)")
168
- }
169
- var mutableComponents = components
170
- if !queryItems.isEmpty {
171
- mutableComponents.queryItems = queryItems
121
+ var body: Data? = nil
122
+ var cachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy
123
+ var timeoutInterval: TimeInterval = 30
124
+
125
+ func urlRequest(relativeTo baseURL: URL) throws -> URLRequest {
126
+ guard var parts = URLComponents(url: baseURL.appending(path: path),
127
+ resolvingAgainstBaseURL: true) else {
128
+ throw NetworkError.invalidURL
172
129
  }
173
- guard let url = mutableComponents.url else {
174
- preconditionFailure("Failed to construct URL from components")
175
- }
176
- return url
130
+ if !queryItems.isEmpty { parts.queryItems = queryItems }
131
+ guard let url = parts.url else { throw NetworkError.invalidURL }
132
+ var request = URLRequest(url: url, cachePolicy: cachePolicy, timeoutInterval: timeoutInterval)
133
+ request.httpMethod = method.rawValue.uppercased()
134
+ request.httpBody = body
135
+ request.allHTTPHeaderFields = headers
136
+ return request
177
137
  }
178
138
  }
179
139
  ```
180
140
 
181
- The client accepts a `baseURL`, optional custom `URLSession`, `JSONDecoder`,
182
- and an array of `RequestMiddleware` interceptors. Each method builds a
183
- `URLRequest` from the endpoint, applies middleware, executes the request,
184
- validates the status code, and decodes the result. See
185
- [references/urlsession-patterns.md](references/urlsession-patterns.md) for the complete `APIClient` implementation
186
- with convenience methods, request builder, and test setup.
141
+ A bad URL is a thrown `NetworkError.invalidURL`, never a crash; nothing in
142
+ this skill force-unwraps.
187
143
 
188
- Production clients should receive an injected, configured `URLSession` instead
189
- of calling `URLSession.shared` internally. Configure `URLSessionConfiguration`
190
- with request/resource timeouts, cache policy or `URLCache`,
191
- `waitsForConnectivity`, data-cost policy, and delegates when authentication
192
- challenges, redirects, metrics, pinning, or background transfer handling matter.
144
+ The concrete `APIClient` is built from a base URL, an injected `URLSession`
145
+ and an ordered list of `RequestMiddleware`; it owns a snake_case, ISO 8601
146
+ `JSONDecoder` and `JSONEncoder`. Every method runs
147
+ the same pipeline: build the request, apply middleware, execute, validate,
148
+ decode. The full implementation, a fluent request builder and tests are in
149
+ [URLSession patterns](references/urlsession-patterns.md).
193
150
 
194
- ### Lightweight Closure-Based Client
151
+ Inject a configured session into production clients rather than reaching for
152
+ `URLSession.shared` inside them. The configuration is where request and
153
+ resource timeouts, cache policy or a `URLCache`, `waitsForConnectivity`,
154
+ cellular and Low Data Mode policy live, plus a delegate when you need auth
155
+ challenges, redirect control, metrics, pinning or background transfers.
195
156
 
196
- For apps using the MV pattern, use closure-based clients for testability
197
- and SwiftUI preview support. See [references/lightweight-clients.md](references/lightweight-clients.md) for
198
- the full pattern (struct of async closures, injected via init).
157
+ ### Lightweight closure-based client
199
158
 
200
- ### Request Middleware / Interceptors
159
+ MV-style SwiftUI apps often do better with a struct of async closures passed
160
+ in through `init`: trivial to stub in previews and tests. See
161
+ [lightweight clients](references/lightweight-clients.md).
201
162
 
202
- Middleware transforms requests before they are sent. Use this for
203
- authentication, logging, analytics headers, and similar cross-cutting
204
- concerns.
163
+ ### Request middleware
164
+
165
+ Middleware rewrites a request before it goes out. Authentication, logging,
166
+ tracing and analytics headers all fit here.
205
167
 
206
168
  ```swift
207
169
  protocol RequestMiddleware: Sendable {
208
- func prepare(_ request: URLRequest) async throws -> URLRequest
170
+ func adapt(_ request: inout URLRequest) async throws
209
171
  }
210
- ```
211
172
 
212
- ```swift
213
- struct AuthMiddleware: RequestMiddleware {
214
- let tokenProvider: @Sendable () async throws -> String
173
+ struct BearerTokenMiddleware: RequestMiddleware {
174
+ let currentToken: @Sendable () async throws -> String
215
175
 
216
- func prepare(_ request: URLRequest) async throws -> URLRequest {
217
- var request = request
218
- let token = try await tokenProvider()
219
- request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
220
- return request
176
+ func adapt(_ request: inout URLRequest) async throws {
177
+ let token = try await currentToken()
178
+ request.addValue("Bearer " + token, forHTTPHeaderField: "Authorization")
221
179
  }
222
180
  }
223
181
  ```
224
182
 
225
- ### Token Refresh Flow
183
+ ### Token refresh
226
184
 
227
- Handle 401 responses by refreshing the token and retrying once.
185
+ On a 401, refresh once and repeat the call once. A second 401 is a real
186
+ failure, not a loop.
228
187
 
229
188
  ```swift
230
- func fetchWithTokenRefresh<T: Decodable & Sendable>(
231
- _ type: T.Type,
232
- endpoint: Endpoint,
233
- tokenStore: TokenStore
234
- ) async throws -> T {
189
+ func fetchRefreshingOnce<Reply: Decodable & Sendable>(_ endpoint: Endpoint, as kind: Reply.Type) async throws -> Reply {
235
190
  do {
236
- return try await fetch(type, endpoint: endpoint)
237
- } catch NetworkError.httpError(statusCode: 401, _) {
238
- try await tokenStore.refreshToken()
239
- return try await fetch(type, endpoint: endpoint)
191
+ return try await client.fetch(endpoint, as: kind)
192
+ } catch NetworkError.httpStatus(code: 401, _, _) {
193
+ try await credentials.refresh()
194
+ return try await client.fetch(endpoint, as: kind)
240
195
  }
241
196
  }
242
197
  ```
243
198
 
244
- ## Error Handling
199
+ ## Error handling
245
200
 
246
- ### Structured Error Types
201
+ ### Structured error type
202
+
203
+ One error enum is shared by this file and every reference:
247
204
 
248
205
  ```swift
249
- enum NetworkError: Error, Sendable {
206
+ enum NetworkError: Error, LocalizedError {
207
+ case invalidURL
250
208
  case invalidResponse
251
- case httpError(statusCode: Int, data: Data)
252
- case decodingFailed(Error)
209
+ case httpStatus(code: Int, body: Data, message: String?)
210
+ case decodingFailed(any Error)
253
211
  case noConnection
254
212
  case timedOut
255
213
  case cancelled
256
-
257
- /// Map a URLError to a typed NetworkError
258
- static func from(_ urlError: URLError) -> NetworkError {
259
- switch urlError.code {
260
- case .notConnectedToInternet, .networkConnectionLost:
261
- return .noConnection
262
- case .timedOut:
263
- return .timedOut
264
- case .cancelled:
265
- return .cancelled
266
- default:
267
- return .httpError(statusCode: -1, data: Data())
214
+ case transport(URLError)
215
+
216
+ static func from(_ error: URLError) -> NetworkError {
217
+ switch error.code {
218
+ case .notConnectedToInternet, .networkConnectionLost: .noConnection
219
+ case .timedOut: .timedOut
220
+ case .cancelled: .cancelled
221
+ default: .transport(error)
268
222
  }
269
223
  }
270
224
  }
271
225
  ```
272
226
 
273
- ### Key URLError Cases
227
+ `Error` already refines `Sendable`, so the enum is Sendable without saying
228
+ so. Unknown transport failures keep their `URLError` in `.transport` instead
229
+ of posing as a fake HTTP status. `errorDescription` returns `nil` for
230
+ `.cancelled` so cancellation never surfaces as a message; the full
231
+ implementation is in [URLSession patterns](references/urlsession-patterns.md#error-types).
274
232
 
275
- | URLError Code | Meaning | Action |
233
+ ### URLError codes worth handling
234
+
235
+ | Code | What happened | Response |
276
236
  |---|---|---|
277
- | `.notConnectedToInternet` | Device offline | Show offline UI, queue for retry |
278
- | `.networkConnectionLost` | Connection dropped mid-request | Retry with backoff |
279
- | `.timedOut` | Server did not respond in time | Retry once, then show error |
280
- | `.cancelled` | Task was cancelled | No action needed; do not show error |
281
- | `.cannotFindHost` | DNS failure | Check URL, show error |
282
- | `.secureConnectionFailed` | TLS handshake failed | Check cert pinning, ATS config |
283
- | `.userAuthenticationRequired` | Authentication required to access a resource | Trigger auth flow |
237
+ | `.notConnectedToInternet` | Device is offline | Offline UI, queue for later |
238
+ | `.networkConnectionLost` | The connection broke partway through | Back off, then try again |
239
+ | `.timedOut` | Server too slow | Retry once, then report |
240
+ | `.cancelled` | Task was cancelled | Do nothing, show nothing |
241
+ | `.cannotFindHost` | DNS lookup failed | Check the host, report |
242
+ | `.secureConnectionFailed` | TLS handshake failed | Check ATS and pinning setup |
243
+ | `.userAuthenticationRequired` | Resource needs credentials | Start sign-in |
244
+
245
+ ### Server error bodies
284
246
 
285
- ### Decoding Server Error Bodies
247
+ Many APIs return a JSON body with the failure. The client decodes it into
248
+ `message` when it can; a caller can also decode it on demand:
286
249
 
287
250
  ```swift
288
- struct APIErrorResponse: Decodable, Sendable {
289
- let code: String
290
- let message: String
291
- }
251
+ struct APIErrorBody: Decodable, Sendable {
252
+ let code: String?
253
+ let message: String?
292
254
 
293
- func decodeAPIError(from data: Data) -> APIErrorResponse? {
294
- try? JSONDecoder().decode(APIErrorResponse.self, from: data)
255
+ static func decode(from data: Data) -> APIErrorBody? {
256
+ try? JSONDecoder().decode(APIErrorBody.self, from: data)
257
+ }
295
258
  }
296
259
 
297
- // Usage in catch block
298
- catch NetworkError.httpError(let statusCode, let data) {
299
- if let apiError = decodeAPIError(from: data) {
300
- showError("Server error: \(apiError.message)")
301
- } else {
302
- showError("HTTP \(statusCode)")
303
- }
260
+ do { try await client.perform(archiveEndpoint) }
261
+ catch NetworkError.httpStatus(let status, let body, let message) {
262
+ banner = message ?? APIErrorBody.decode(from: body)?.message ?? "Request failed (\(status))"
304
263
  }
305
264
  ```
306
265
 
307
- ### Retry with Exponential Backoff
266
+ ### Retry with exponential backoff
308
267
 
309
- Use structured concurrency for retries. Respect task cancellation between
310
- attempts. Skip retries for cancellation and 4xx client errors (except 429).
268
+ Retry inside structured concurrency so cancellation stops the loop. Never
269
+ retry cancellation or a 4xx, with 429 as the exception; 5xx and transient
270
+ transport errors are fair game.
311
271
 
312
272
  ```swift
313
273
  func withRetry<T: Sendable>(
314
274
  maxAttempts: Int = 3,
315
275
  initialDelay: Duration = .seconds(1),
276
+ maxDelay: Duration = .seconds(30),
277
+ shouldRetry: @Sendable (any Error) -> Bool = isTransient,
316
278
  operation: @Sendable () async throws -> T
317
279
  ) async throws -> T {
318
- var lastError: Error?
280
+ precondition(maxAttempts > 0, "maxAttempts must be at least 1")
319
281
  for attempt in 0..<maxAttempts {
282
+ try Task.checkCancellation()
320
283
  do {
321
284
  return try await operation()
322
285
  } catch {
323
- lastError = error
324
- if error is CancellationError { throw error }
325
- if case NetworkError.httpError(let code, _) = error,
326
- (400..<500).contains(code), code != 429 { throw error }
327
- if attempt < maxAttempts - 1 {
328
- try await Task.sleep(for: initialDelay * Int(pow(2.0, Double(attempt))))
329
- }
286
+ guard attempt < maxAttempts - 1, shouldRetry(error) else { throw error }
287
+ let capped = min(initialDelay * (1 << attempt), maxDelay)
288
+ try await Task.sleep(for: capped + capped * Double.random(in: 0...0.1))
330
289
  }
331
290
  }
332
- throw lastError!
291
+ preconditionFailure("the last attempt always returns or throws")
333
292
  }
334
293
  ```
335
294
 
295
+ There is no sleep after the last attempt: its error is rethrown at once. The
296
+ `isTransient` predicate is in
297
+ [URLSession patterns](references/urlsession-patterns.md#retries-that-back-off-exponentially).
298
+
336
299
  ## Pagination
337
300
 
338
- Build cursor-based or offset-based pagination with `AsyncSequence`.
339
- Always check `Task.isCancelled` between pages. See
340
- [references/urlsession-patterns.md](references/urlsession-patterns.md) for complete `CursorPaginator` and
341
- offset-based implementations.
301
+ Model paging as an `AsyncSequence` of pages, cursor-based or offset-based,
302
+ and call `try Task.checkCancellation()` (or test `Task.isCancelled`) before
303
+ every page request. Both paginators are written out in
304
+ [URLSession patterns](references/urlsession-patterns.md#paging-by-cursor).
342
305
 
343
- ## Network Reachability
306
+ ## Network reachability
344
307
 
345
- Use `NWPathMonitor` from the Network framework -- not third-party
346
- Reachability libraries. On current OS targets it conforms to `AsyncSequence`;
347
- wrap `pathUpdateHandler` only for compatibility or custom projections.
308
+ Use `NWPathMonitor`, not a third-party Reachability port. On current SDKs the
309
+ monitor is itself an `AsyncSequence`; wrapping `pathUpdateHandler` in your own
310
+ stream is only for older targets or for a custom projection.
348
311
 
349
312
  ```swift
350
- import Network
351
-
352
- func observeNetworkStatus() async {
353
- let monitor = NWPathMonitor()
354
-
355
- for await path in monitor {
356
- handle(path.status)
357
- }
313
+ for await path in NWPathMonitor() {
314
+ isOnline = path.status == .satisfied
315
+ useLowBandwidth = path.isConstrained || path.isExpensive
358
316
  }
359
317
  ```
360
318
 
361
- Check `path.isExpensive` (cellular) and `path.isConstrained` (Low Data
362
- Mode) to adapt behavior (reduce image quality, skip prefetching).
319
+ `isExpensive` means cellular or a hotspot; `isConstrained` means Low Data
320
+ Mode. Use them to lower image quality or skip prefetching.
363
321
 
364
- Use Network.framework for low-level TCP, UDP, listeners, Bonjour, path
365
- monitoring, or WebSocket protocol work -- not ordinary REST APIs. For iOS 26
366
- `NetworkConnection<QUIC>`, `openStream(...)` and `inboundStreams(...)` are
367
- async throwing APIs; see [references/network-framework.md#quic-multiplexed-streams](references/network-framework.md#quic-multiplexed-streams).
322
+ Network.framework is for TCP and UDP, listeners, Bonjour, path monitoring and
323
+ WebSocket protocol work. It is not the tool for an ordinary REST API. On
324
+ iOS 26 `NetworkConnection<QUIC>` adds multiplexed streams, and both
325
+ `openStream(...)` and `inboundStreams(...)` are async and throwing. See
326
+ [Network framework guide](references/network-framework.md) and its
327
+ [QUIC stream notes](references/network-framework.md#quic-multiplexed-streams).
368
328
 
369
329
  ## Configuring URLSession
370
330
 
371
- Create a configured session for production code. `URLSession.shared` is
372
- acceptable only for simple, one-off requests.
331
+ `URLSession.shared` suits a quick one-off call. Anything that ships deserves a
332
+ configured session:
373
333
 
374
334
  ```swift
375
- let configuration = URLSessionConfiguration.default
376
- configuration.timeoutIntervalForRequest = 30
377
- configuration.timeoutIntervalForResource = 300
378
- configuration.waitsForConnectivity = true
379
- configuration.requestCachePolicy = .returnCacheDataElseLoad
380
- configuration.httpAdditionalHeaders = [
335
+ let config = URLSessionConfiguration.default
336
+ config.timeoutIntervalForRequest = 30
337
+ config.timeoutIntervalForResource = 300
338
+ config.waitsForConnectivity = true
339
+ config.requestCachePolicy = .returnCacheDataElseLoad
340
+ config.httpAdditionalHeaders = [
381
341
  "Accept": "application/json",
382
- "Accept-Language": Locale.preferredLanguages.first ?? "en"
342
+ "Accept-Language": Locale.preferredLanguages.prefix(3).joined(separator: ", "),
383
343
  ]
384
-
385
- let session = URLSession(configuration: configuration)
344
+ let session = URLSession(configuration: config)
386
345
  ```
387
346
 
388
- `waitsForConnectivity = true` is valuable -- it makes the session wait for
389
- a network path instead of failing immediately when offline. Combine with
390
- `urlSession(_:taskIsWaitingForConnectivity:)` delegate callback for UI
391
- feedback.
347
+ With `waitsForConnectivity`, a request made while offline waits for a usable
348
+ path instead of failing immediately. Implement
349
+ `urlSession(_:taskIsWaitingForConnectivity:)` on the delegate to tell the user.
392
350
 
393
351
  ## App Transport Security (ATS)
394
352
 
395
- ATS enforces HTTPS for all connections by default. Do not disable it.
396
- ATS is URL Loading System policy, so it covers `URLSession` rather than making
397
- lower-level `Network.framework` connections secure automatically. When using
398
- Network.framework, configure secure TLS parameters and trust handling correctly
399
- for that protocol stack.
400
-
401
- Use domain-specific ATS exceptions only as a last resort.
402
-
403
- **Rules:**
404
- - Never set `NSAllowsArbitraryLoads` to `true` in production unless there is no narrower option.
405
- - ATS exceptions require justification and may trigger additional App Store review.
406
- - Use exception domains only for third-party servers you cannot upgrade to HTTPS.
407
- - `NSAllowsLocalNetworking` is acceptable for local device communication (Bonjour, IoT).
408
- - Prefer ATS `NSPinnedDomains` for declarative pinning when possible. Raw bytes
409
- from `SecKeyCopyExternalRepresentation` are not sufficient for SPKI pinning;
410
- correct SPKI pinning hashes Subject Public Key Info and belongs in `swift-security`.
411
-
412
- ## Common Mistakes
413
-
414
- **DON'T:** Use `URLSession.shared` with custom configuration needs.
415
- **DO:** Create a configured `URLSession` with appropriate timeouts, caching,
416
- and delegate for production code.
417
-
418
- **DON'T:** Force-unwrap `URL(string:)` with dynamic input.
419
- **DO:** Use `URL(string:)` with proper error handling. Force-unwrap is
420
- acceptable only for compile-time-constant strings.
421
-
422
- **DON'T:** Decode JSON on the main thread for large payloads.
423
- **DO:** Keep decoding on the calling context of the URLSession call, which
424
- is off-main by default. Only hop to `@MainActor` to update UI state.
425
-
426
- **DON'T:** Ignore cancellation in long-running network tasks.
427
- **DO:** Check `Task.isCancelled` or call `try Task.checkCancellation()` in
428
- loops (pagination, streaming, retry). Use `.task` in SwiftUI for automatic
429
- cancellation.
430
-
431
- **DON'T:** Use Alamofire or Moya when URLSession async/await handles the
432
- need.
433
- **DO:** Use URLSession directly. With async/await, the ergonomic gap that
434
- justified third-party libraries no longer exists. Reserve third-party
435
- libraries for genuinely missing features (e.g., image caching).
436
-
437
- **DON'T:** Mock URLSession directly in tests.
438
- **DO:** Use `URLProtocol` subclass for transport-level mocking, or use
439
- protocol-based clients that accept a test double.
440
-
441
- **DON'T:** Use `data(for:)` for large file downloads.
442
- **DO:** Use `download(for:)` which streams to disk and avoids memory spikes.
443
-
444
- **DON'T:** Fire network requests from `body` or view initializers.
445
- **DO:** Use `.task` or `.task(id:)` to trigger network calls.
446
-
447
- **DON'T:** Hardcode authentication tokens in requests.
448
- **DO:** Inject tokens via middleware so they are centralized and refreshable.
449
-
450
- **DON'T:** Ignore HTTP status codes and decode blindly.
451
- **DO:** Validate status codes before decoding. A 200 with invalid JSON and
452
- a 500 with an error body require different handling.
453
-
454
- ## Review Checklist
455
-
456
- - [ ] Foreground transfers use async/await; background sessions use delegate/task APIs
457
- - [ ] Error handling covers URLError cases (.notConnectedToInternet, .timedOut, .cancelled)
458
- - [ ] Requests are cancellable (respect Task cancellation via `.task` modifier or stored Task references)
459
- - [ ] Authentication tokens injected via middleware, not hardcoded
460
- - [ ] Response HTTP status codes validated before decoding
461
- - [ ] Large downloads use `download(for:)` not `data(for:)`
462
- - [ ] Network calls happen off `@MainActor` (only UI updates on main)
463
- - [ ] URLSession configured with appropriate timeouts and caching
464
- - [ ] Production clients inject configured sessions instead of using `URLSession.shared`
465
- - [ ] Background transfers use task/delegate APIs, not async convenience APIs
466
- - [ ] Retry logic excludes cancellation and 4xx client errors
467
- - [ ] Pagination checks `Task.isCancelled` between pages
468
- - [ ] Sensitive tokens stored in Keychain (not UserDefaults or plain files)
469
- - [ ] No force-unwrapped URLs from dynamic input
470
- - [ ] Server error responses decoded and surfaced to users
471
- - [ ] Network.framework code configures TLS/trust explicitly and keeps deep pinning work in `swift-security`
472
- - [ ] `NetworkConnection<QUIC>` stream APIs are treated as async throwing
473
- - [ ] Ensure network response model types conform to Sendable; use @MainActor for UI-updating completion paths
353
+ - ATS requires HTTPS by default. Leave it on.
354
+ - ATS is policy of the URL Loading System. It governs URLSession, not
355
+ Network.framework connections; with Network.framework you configure TLS
356
+ and trust evaluation yourself.
357
+ - A per-domain exception is the last resort, only for a third-party host
358
+ that cannot move to HTTPS. Exceptions need a written justification and
359
+ can draw extra App Review scrutiny.
360
+ - `NSAllowsArbitraryLoads = true` does not belong in a production build
361
+ unless nothing narrower exists.
362
+ - `NSAllowsLocalNetworking` is fine for talking to local devices (Bonjour,
363
+ IoT accessories).
364
+ - Prefer declarative pinning with `NSPinnedDomains`. Hashing the raw bytes
365
+ from `SecKeyCopyExternalRepresentation` is not SPKI pinning; a correct pin
366
+ hashes the Subject Public Key Info. Trust code belongs to `swift-security`.
367
+
368
+ ## Common mistakes
369
+
370
+ 1. `URLSession.shared` where configuration matters. Build a session with
371
+ timeouts, caching and a delegate.
372
+ 2. Force-unwrapping `URL(string:)`. Handle `nil`, whether the string is
373
+ dynamic or a literal; a thrown `invalidURL` costs one line.
374
+ 3. Decoding big payloads on the main actor. The awaited call and the decode
375
+ stay off the main actor; hop to `@MainActor` only to publish UI state.
376
+ 4. Ignoring cancellation in paging, streaming and retry loops. Test
377
+ `Task.isCancelled` or use `try Task.checkCancellation()`, and start work
378
+ from SwiftUI `.task` so it is cancelled with the view.
379
+ 5. Adding Alamofire or Moya for what async URLSession already does. Keep
380
+ libraries for features the SDK lacks, such as image caching.
381
+ 6. Mocking URLSession in tests. Stub the transport with a `URLProtocol`
382
+ subclass or swap the client protocol for a double.
383
+ 7. `data(for:)` for large files. `download(for:)` avoids the memory spike.
384
+ 8. Starting requests in a view's `body` or `init`. Use `.task` or
385
+ `.task(id:)`.
386
+ 9. Pasting tokens into individual requests. Middleware centralises them and
387
+ makes refresh possible.
388
+ 10. Decoding before checking the status. A 200 with malformed JSON and a 500
389
+ with an error body need different handling.
390
+
391
+ ## Review checklist
392
+
393
+ - [ ] Foreground work uses async/await; background sessions use tasks and delegates, not async conveniences
394
+ - [ ] `.notConnectedToInternet`, `.timedOut` and `.cancelled` are handled
395
+ - [ ] Requests are cancellable through `.task` or a stored `Task`
396
+ - [ ] Auth headers come from middleware; tokens live in the Keychain, never UserDefaults or plain files
397
+ - [ ] Status code checked before decoding; server error bodies decoded and shown
398
+ - [ ] Large downloads use `download(for:)`
399
+ - [ ] Networking and decoding run off `@MainActor`; only UI updates hop to main
400
+ - [ ] Session has deliberate timeouts and caching and is injected, not `URLSession.shared`
401
+ - [ ] Retry skips cancellation and 4xx other than 429
402
+ - [ ] Paginators check cancellation between pages
403
+ - [ ] No force-unwrapped URLs or casts
404
+ - [ ] Network.framework code sets TLS and trust explicitly; pinning detail deferred to `swift-security`
405
+ - [ ] `NetworkConnection<QUIC>` stream calls are awaited and can throw
406
+ - [ ] Response models are `Sendable`; code that updates UI is `@MainActor`
474
407
 
475
408
  ## References
476
409
 
477
- - See [references/urlsession-patterns.md](references/urlsession-patterns.md) for complete API client
478
- implementation, multipart uploads, download progress, URLProtocol
479
- mocking, retry/backoff, certificate pinning, request logging, and
480
- pagination implementations.
481
- - See [references/background-websocket.md](references/background-websocket.md) for background URLSession
482
- configuration, background downloads/uploads, WebSocket patterns with
483
- structured concurrency, and reconnection strategies.
484
- - See [references/lightweight-clients.md](references/lightweight-clients.md) for the lightweight closure-based
485
- client pattern (struct of async closures, injected via init for testability
486
- and preview support).
487
- - See [references/network-framework.md](references/network-framework.md) for Network.framework (NWConnection,
488
- NWListener, NWBrowser, NWPathMonitor) and low-level TCP/UDP/WebSocket patterns.
489
- - See [references/file-storage-patterns.md](references/file-storage-patterns.md) for file system directory
490
- selection, FileProtectionType, backup exclusion, and storage pressure handling.
410
+ - [URLSession patterns](references/urlsession-patterns.md): full API client, request builder, multipart upload, download progress, cursor and offset pagination, URLProtocol mocking, retry with jitter, pinning guidance, request logging, caching, SSE, session factory.
411
+ - [background and WebSocket guide](references/background-websocket.md): background session setup, background downloads and uploads, app delegate event handling, WebSocket with structured concurrency, reconnection, typed messages, auth.
412
+ - [lightweight clients](references/lightweight-clients.md): struct-of-closures client injected through `init` for tests and previews.
413
+ - [Network framework guide](references/network-framework.md): `NWConnection`, `NWListener`, `NWBrowser`, `NWPathMonitor`, raw TCP, UDP and WebSocket, iOS 26 `NetworkConnection`.
414
+ - [file storage patterns](references/file-storage-patterns.md): directory choice, `FileProtectionType`, backup exclusion, low-storage handling.