@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +0 -10
  3. package/docs/facts.json +1 -1
  4. package/manifest.json +266 -267
  5. package/package.json +2 -2
  6. package/pipeline/scripts/_notices.mjs +1 -1
  7. package/pipeline/skills/.skill-manifest.json +68 -68
  8. package/pipeline/skills/shared/README.md +70 -70
  9. package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
  10. package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
  11. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
  12. package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
  13. package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
  14. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
  15. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
  16. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
  17. package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
  18. package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
  19. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
  20. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
  21. package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
  22. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
  23. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
  24. package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
  25. package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
  26. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
  27. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
  28. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
  29. package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
  30. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
  31. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
  32. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
  33. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
  34. package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
  35. package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
  36. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
  37. package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
  38. package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
  39. package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
  40. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
  41. package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
  42. package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
  43. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
  44. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
  45. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
  46. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
  47. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
  48. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
  49. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
  50. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
  51. package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
  52. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
  53. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
  54. package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
  55. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
  56. package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
  57. package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
  58. package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
  59. package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
  60. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
  61. package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
  62. package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
  63. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
  64. package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
  65. package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
  66. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
  67. package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
  68. package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
  69. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
  70. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
  71. package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
  72. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
  73. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
  74. package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
  75. package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
  76. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
  77. package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
  78. package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
  79. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
  80. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
  81. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
  82. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
  83. package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
  84. package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
  85. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
  86. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
  87. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
  88. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
  89. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
  90. package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
  91. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
  92. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
  93. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
  94. package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
  95. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
  96. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
  97. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
  98. package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
  99. package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
  100. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
  101. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
  102. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
  103. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
  104. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
  105. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
  106. package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
  107. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
  108. package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
  109. package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
  110. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
  111. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
  112. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
  113. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
  114. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
  115. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
  116. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
  117. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
  118. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
  119. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
  120. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
  121. package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
  122. package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
  123. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
  124. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
  125. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
  126. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
  127. package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
  128. package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
  129. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
  130. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
  131. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
  132. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
  133. package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
  134. package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
  135. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
  136. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
  137. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
  138. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
  139. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
  140. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
  141. package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
  142. package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
  143. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
  144. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
  145. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
  146. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
  147. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
  148. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
  149. package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
  150. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
  151. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
  152. package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
  153. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
  154. package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
  155. package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
  156. package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
  157. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
  158. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
  159. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
  160. package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
  161. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
  162. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
  163. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
  164. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
  165. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
  166. package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
  167. package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
  168. package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
  169. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
  170. package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
  171. package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
  172. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
  173. package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
  174. package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
  175. package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
  176. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
  177. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
  178. package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
  179. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
  180. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
  181. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
  182. package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
  183. package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
  184. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
  185. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
  186. package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
  187. package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
  188. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
  189. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
  190. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
  191. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
  192. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
  193. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
  194. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
  195. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
  196. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
  197. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
  198. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
  199. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
  200. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
  201. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
  202. package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
  203. package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
  204. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
  205. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
  206. package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
  207. package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
  208. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
  209. package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
  210. package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
  211. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
  212. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
  213. package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
  214. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
  215. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
  216. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
  217. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
  218. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
  219. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
  220. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
  221. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
  222. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
  223. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
  224. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
  225. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
  226. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
  227. package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
  228. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
  229. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
  230. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
  231. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
  232. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
  233. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
  234. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
  235. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
  236. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
  237. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
  238. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
  239. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
  240. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
  241. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
  242. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
  243. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
  244. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
  245. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
  246. package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
  247. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
  248. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
  249. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
  250. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
  251. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
  252. package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
  253. package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
  254. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
  255. package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
  256. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
  257. package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
  258. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
  259. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
  260. package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
  261. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
  262. package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
  263. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
  264. package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
@@ -1,567 +1,448 @@
1
- # WeatherKit Extended Patterns
1
+ # WeatherKit Patterns
2
2
 
3
- Overflow reference for the `weatherkit` skill. Contains advanced patterns that exceed the main skill file's scope.
3
+ Larger building blocks for the `weatherkit` skill. Its SKILL.md covers setup,
4
+ queries, alerts, availability and the attribution rules; this file puts them
5
+ together.
4
6
 
5
7
  ## Contents
6
8
 
7
- - [WeatherKit SwiftUI Integration](#weatherkit-swiftui-integration)
8
- - [Charts Integration](#charts-integration)
9
- - [Historical Weather Statistics](#historical-weather-statistics)
10
- - [Weather Condition Mapping](#weather-condition-mapping)
11
- - [Caching Strategy](#caching-strategy)
12
- - [Location-Based Weather](#location-based-weather)
9
+ - [WeatherKit SwiftUI integration](#weatherkit-swiftui-integration)
10
+ - [Attribution view](#attribution-view)
11
+ - [Charts integration](#charts-integration)
12
+ - [Historical weather statistics](#historical-weather-statistics)
13
+ - [Weather condition mapping](#weather-condition-mapping)
14
+ - [Caching strategy](#caching-strategy)
15
+ - [Location-based weather](#location-based-weather)
13
16
 
14
- ## WeatherKit SwiftUI Integration
17
+ ## WeatherKit SwiftUI integration
15
18
 
16
- ### Weather Manager with `@Observable`
19
+ ### A weather model
20
+
21
+ One `@Observable` type fetches the datasets a dashboard needs in a single call,
22
+ together with the attribution, and exposes loading and error state.
17
23
 
18
24
  ```swift
19
- import WeatherKit
20
25
  import CoreLocation
26
+ import Observation
27
+ import WeatherKit
21
28
 
22
- @Observable
23
29
  @MainActor
24
- final class WeatherManager {
25
- private let service = WeatherService.shared
26
-
27
- var current: CurrentWeather?
28
- var hourlyForecast: Forecast<HourWeather>?
29
- var dailyForecast: Forecast<DayWeather>?
30
- var alerts: [WeatherAlert]?
31
- var attribution: WeatherAttribution?
32
- var isLoading = false
33
- var error: Error?
34
-
35
- func fetchWeather(for location: CLLocation) async {
36
- isLoading = true
37
- error = nil
38
-
30
+ @Observable
31
+ final class ForecastBoard {
32
+ private(set) var now: CurrentWeather?
33
+ private(set) var hours: Forecast<HourWeather>?
34
+ private(set) var days: Forecast<DayWeather>?
35
+ private(set) var alerts: [WeatherAlert] = []
36
+ private(set) var credit: WeatherAttribution?
37
+ private(set) var busy = false
38
+ private(set) var failure: (any Error)?
39
+
40
+ private let source = WeatherService.shared
41
+
42
+ func load(for place: CLLocation) async {
43
+ busy = true
44
+ defer { busy = false }
39
45
  do {
40
- let (current, hourly, daily, alerts) = try await service.weather(
41
- for: location,
42
- including: .current, .hourly, .daily, .alerts
43
- )
44
- self.current = current
45
- self.hourlyForecast = hourly
46
- self.dailyForecast = daily
47
- self.alerts = alerts
48
- self.attribution = try await service.attribution
46
+ let (current, hourly, daily, warnings) = try await source.weather(
47
+ for: place, including: .current, .hourly, .daily, .alerts)
48
+ now = current
49
+ hours = hourly
50
+ days = daily
51
+ alerts = warnings ?? []
52
+ credit = try await source.attribution
53
+ failure = nil
49
54
  } catch {
50
- self.error = error
55
+ failure = error
51
56
  }
52
-
53
- isLoading = false
54
57
  }
55
58
  }
56
59
  ```
57
60
 
58
- ### Weather Dashboard View
61
+ ### Dashboard view
59
62
 
60
63
  ```swift
61
- import SwiftUI
62
- import WeatherKit
63
-
64
- struct WeatherDashboardView: View {
65
- @Environment(WeatherManager.self) private var manager
66
- let location: CLLocation
64
+ struct ForecastScreen: View {
65
+ @State private var board = ForecastBoard()
66
+ let place: CLLocation
67
67
 
68
68
  var body: some View {
69
- NavigationStack {
70
- ScrollView {
71
- VStack {
72
- if manager.isLoading {
73
- ProgressView("Loading weather...")
74
- } else if let current = manager.current {
75
- currentConditionsCard(current)
76
- }
77
-
78
- if let hourly = manager.hourlyForecast {
79
- hourlyForecastSection(hourly)
80
- }
81
-
82
- if let daily = manager.dailyForecast {
83
- dailyForecastSection(daily)
84
- }
85
-
86
- if let alerts = manager.alerts, !alerts.isEmpty {
87
- alertsSection(alerts)
88
- }
89
-
90
- if let attribution = manager.attribution {
91
- WeatherAttributionView(attribution: attribution)
92
- }
69
+ ScrollView {
70
+ if board.busy && board.now == nil {
71
+ ProgressView().padding(.top, 80)
72
+ } else {
73
+ VStack(alignment: .leading, spacing: 24) {
74
+ if let now = board.now { NowPanel(now: now) }
75
+ if let hours = board.hours { HourStrip(hours: hours) }
76
+ if let days = board.days { DayList(days: days) }
77
+ if !board.alerts.isEmpty { AlertList(alerts: board.alerts) }
78
+ if let credit = board.credit { CreditFooter(credit: credit) }
93
79
  }
94
80
  .padding()
95
81
  }
96
- .navigationTitle("Weather")
97
- .task {
98
- await manager.fetchWeather(for: location)
99
- }
100
- .refreshable {
101
- await manager.fetchWeather(for: location)
102
- }
103
82
  }
83
+ .task { await board.load(for: place) }
84
+ .refreshable { await board.load(for: place) }
104
85
  }
86
+ }
87
+ ```
105
88
 
106
- private func currentConditionsCard(_ current: CurrentWeather) -> some View {
107
- VStack {
108
- Image(systemName: current.symbolName)
109
- .font(.system(size: 60))
110
- .symbolRenderingMode(.multicolor)
89
+ The panels:
90
+
91
+ ```swift
92
+ struct NowPanel: View {
93
+ let now: CurrentWeather
111
94
 
112
- Text(current.temperature.formatted())
113
- .font(.system(size: 48, weight: .thin))
114
-
115
- Text(current.condition.description)
116
- .font(.title3)
117
- .foregroundStyle(.secondary)
118
-
119
- HStack {
120
- Label(
121
- "Humidity \(current.humidity.formatted(.percent))",
122
- systemImage: "humidity"
123
- )
124
- Label(
125
- "Wind \(current.wind.speed.formatted())",
126
- systemImage: "wind"
127
- )
128
- Label(
129
- "UV \(current.uvIndex.value)",
130
- systemImage: "sun.max"
131
- )
95
+ var body: some View {
96
+ HStack(spacing: 16) {
97
+ Image(systemName: now.symbolName)
98
+ .symbolRenderingMode(.multicolor)
99
+ .font(.system(size: 56))
100
+ VStack(alignment: .leading) {
101
+ Text(now.temperature.formatted()).font(.largeTitle.bold())
102
+ Text(now.condition.description)
103
+ Text("Humidity \(now.humidity.formatted(.percent)) Wind \(now.wind.speed.formatted()) UV \(now.uvIndex.value)")
104
+ .font(.footnote)
105
+ .foregroundStyle(.secondary)
132
106
  }
133
- .font(.caption)
134
107
  }
135
- .padding()
136
108
  }
109
+ }
110
+
111
+ struct HourStrip: View {
112
+ let hours: Forecast<HourWeather>
137
113
 
138
- private func hourlyForecastSection(_ forecast: Forecast<HourWeather>) -> some View {
139
- VStack(alignment: .leading) {
140
- Text("Hourly Forecast")
141
- .font(.headline)
142
-
143
- ScrollView(.horizontal, showsIndicators: false) {
144
- HStack {
145
- ForEach(Array(forecast.prefix(12)), id: \.date) { hour in
146
- VStack {
147
- Text(hour.date, format: .dateTime.hour())
148
- .font(.caption)
149
- Image(systemName: hour.symbolName)
150
- .symbolRenderingMode(.multicolor)
151
- Text(hour.temperature.formatted())
152
- .font(.subheadline)
153
- }
114
+ var body: some View {
115
+ ScrollView(.horizontal) {
116
+ LazyHStack(spacing: 18) {
117
+ ForEach(hours.prefix(12), id: \.date) { slot in
118
+ VStack(spacing: 6) {
119
+ Text(slot.date, format: .dateTime.hour())
120
+ Image(systemName: slot.symbolName).symbolRenderingMode(.multicolor)
121
+ Text(slot.temperature.formatted(.measurement(width: .narrow)))
154
122
  }
123
+ .font(.caption)
155
124
  }
156
125
  }
157
126
  }
127
+ .scrollIndicators(.hidden)
158
128
  }
129
+ }
159
130
 
160
- private func dailyForecastSection(_ forecast: Forecast<DayWeather>) -> some View {
161
- VStack(alignment: .leading) {
162
- Text("10-Day Forecast")
163
- .font(.headline)
164
-
165
- ForEach(Array(forecast), id: \.date) { day in
166
- HStack {
167
- Text(day.date, format: .dateTime.weekday(.abbreviated))
168
- .frame(width: 40, alignment: .leading)
169
-
170
- Image(systemName: day.symbolName)
171
- .symbolRenderingMode(.multicolor)
172
- .frame(width: 30)
173
-
174
- Text(day.lowTemperature.formatted())
175
- .foregroundStyle(.secondary)
176
- .frame(width: 50, alignment: .trailing)
177
-
178
- temperatureBar(low: day.lowTemperature, high: day.highTemperature)
131
+ struct DayList: View {
132
+ let days: Forecast<DayWeather>
179
133
 
180
- Text(day.highTemperature.formatted())
181
- .frame(width: 50)
134
+ var body: some View {
135
+ Grid(alignment: .leading, verticalSpacing: 10) {
136
+ ForEach(days.forecast, id: \.date) { entry in
137
+ GridRow {
138
+ Text(entry.date.formatted(.dateTime.weekday(.abbreviated)))
139
+ Image(systemName: entry.symbolName).symbolRenderingMode(.multicolor)
140
+ Text(entry.lowTemperature.formatted(.measurement(width: .narrow)))
141
+ Capsule()
142
+ .fill(LinearGradient(colors: [.teal, .orange], startPoint: .leading, endPoint: .trailing))
143
+ .frame(maxHeight: 5)
144
+ .containerRelativeFrame(.horizontal) { width, _ in width * 0.35 }
145
+ Text(entry.highTemperature.formatted(.measurement(width: .narrow)))
182
146
  }
183
- .font(.subheadline)
184
147
  }
185
148
  }
186
149
  }
150
+ }
187
151
 
188
- private func temperatureBar(
189
- low: Measurement<UnitTemperature>,
190
- high: Measurement<UnitTemperature>
191
- ) -> some View {
192
- Capsule()
193
- .fill(.linearGradient(
194
- colors: [.blue, .orange],
195
- startPoint: .leading,
196
- endPoint: .trailing
197
- ))
198
- .frame(height: 4)
199
- .containerRelativeFrame(.horizontal) { length, _ in
200
- length * 0.3
201
- }
202
- }
152
+ struct AlertList: View {
153
+ let alerts: [WeatherAlert]
203
154
 
204
- private func alertsSection(_ alerts: [WeatherAlert]) -> some View {
205
- VStack(alignment: .leading) {
206
- Text("Weather Alerts")
207
- .font(.headline)
208
-
209
- ForEach(alerts, id: \.detailsURL) { alert in
210
- HStack {
211
- Image(systemName: "exclamationmark.triangle.fill")
212
- .foregroundStyle(alert.severity == .extreme ? .red : .orange)
213
- VStack(alignment: .leading) {
214
- Text(alert.summary)
215
- .font(.subheadline)
216
- Text(alert.region)
217
- .font(.caption)
218
- .foregroundStyle(.secondary)
219
- }
220
- }
221
- .padding()
222
- .background(.yellow.opacity(0.1))
223
- .clipShape(.rect(cornerRadius: 8))
155
+ var body: some View {
156
+ ForEach(alerts, id: \.detailsURL) { warning in
157
+ let isExtreme = warning.severity == .extreme
158
+ Link(destination: warning.detailsURL) {
159
+ Label(warning.summary, systemImage: "exclamationmark.octagon.fill")
160
+ .foregroundStyle(isExtreme ? Color.red : Color.orange)
224
161
  }
225
162
  }
226
163
  }
227
164
  }
228
165
  ```
229
166
 
230
- ### Attribution View
167
+ ## Attribution view
168
+
169
+ A compact footer: a 12-point mark that falls back to the service name while it
170
+ loads, and a link to the data sources.
231
171
 
232
172
  ```swift
233
- struct WeatherAttributionView: View {
234
- let attribution: WeatherAttribution
235
- @Environment(\.colorScheme) private var colorScheme
173
+ struct CreditFooter: View {
174
+ let credit: WeatherAttribution
175
+ @Environment(\.colorScheme) private var scheme
236
176
 
237
177
  var body: some View {
238
- VStack {
239
- AsyncImage(url: markURL) { image in
240
- image
241
- .resizable()
242
- .scaledToFit()
243
- .frame(height: 12)
178
+ HStack(spacing: 8) {
179
+ AsyncImage(url: scheme == .dark ? credit.combinedMarkDarkURL : credit.combinedMarkLightURL) { image in
180
+ image.resizable().scaledToFit()
244
181
  } placeholder: {
245
- Text(attribution.serviceName)
246
- .font(.caption2)
182
+ Text(credit.serviceName)
247
183
  }
184
+ .frame(height: 12)
248
185
 
249
- Link(destination: attribution.legalPageURL) {
250
- Text("Data Sources")
251
- .font(.caption2)
252
- .foregroundStyle(.secondary)
253
- }
186
+ Link("Data Sources", destination: credit.legalPageURL)
254
187
  }
255
- .padding(.vertical)
256
- }
257
-
258
- private var markURL: URL {
259
- colorScheme == .dark
260
- ? attribution.combinedMarkDarkURL
261
- : attribution.combinedMarkLightURL
188
+ .font(.caption2)
189
+ .foregroundStyle(.secondary)
262
190
  }
263
191
  }
264
192
  ```
265
193
 
266
- ## Charts Integration
194
+ ## Charts integration
267
195
 
268
- ### Hourly Temperature Chart
196
+ ### Hourly temperature line
269
197
 
270
198
  ```swift
271
- import SwiftUI
272
199
  import Charts
273
- import WeatherKit
274
200
 
275
- struct HourlyTemperatureChart: View {
276
- let forecast: Forecast<HourWeather>
201
+ struct TemperatureTrend: View {
202
+ let hours: Forecast<HourWeather>
277
203
 
278
204
  var body: some View {
279
- Chart(Array(forecast.prefix(24)), id: \.date) { hour in
280
- LineMark(
281
- x: .value("Hour", hour.date),
282
- y: .value("Temperature", hour.temperature.converted(to: .celsius).value)
283
- )
284
- .interpolationMethod(.catmullRom)
285
- .foregroundStyle(.orange)
286
-
287
- AreaMark(
288
- x: .value("Hour", hour.date),
289
- y: .value("Temperature", hour.temperature.converted(to: .celsius).value)
290
- )
291
- .interpolationMethod(.catmullRom)
292
- .foregroundStyle(.orange.opacity(0.1))
205
+ Chart(hours.prefix(24), id: \.date) { slot in
206
+ let degrees = slot.temperature.converted(to: .celsius).value
207
+ AreaMark(x: .value("Time", slot.date), y: .value("Temperature", degrees))
208
+ .foregroundStyle(.orange.opacity(0.2))
209
+ .interpolationMethod(.catmullRom)
210
+ LineMark(x: .value("Time", slot.date), y: .value("Temperature", degrees))
211
+ .foregroundStyle(.orange)
212
+ .interpolationMethod(.catmullRom)
293
213
  }
294
- .chartYAxisLabel("Temperature (C)")
295
214
  .chartXAxis {
296
215
  AxisMarks(values: .stride(by: .hour, count: 3)) { _ in
297
216
  AxisGridLine()
298
217
  AxisValueLabel(format: .dateTime.hour())
299
218
  }
300
219
  }
301
- .frame(height: 200)
220
+ .frame(minHeight: 160)
302
221
  }
303
222
  }
304
223
  ```
305
224
 
306
- ### Daily Precipitation Chart
225
+ Convert to one unit before plotting; mixing units on one axis gives a
226
+ meaningless line. For display text, keep `formatted()` so the user's unit
227
+ applies.
228
+
229
+ ### Daily precipitation bars
307
230
 
308
231
  ```swift
309
- struct DailyPrecipitationChart: View {
310
- let forecast: Forecast<DayWeather>
232
+ struct RainOutlook: View {
233
+ let days: Forecast<DayWeather>
311
234
 
312
235
  var body: some View {
313
- Chart(Array(forecast), id: \.date) { day in
314
- BarMark(
315
- x: .value("Day", day.date, unit: .day),
316
- y: .value("Chance", day.precipitationChance)
317
- )
318
- .foregroundStyle(.blue.gradient)
236
+ Chart(days.forecast, id: \.date) { entry in
237
+ let odds = entry.precipitationChance
238
+ BarMark(x: .value("Date", entry.date, unit: .day), y: .value("Rain", odds))
239
+ .foregroundStyle(Color.blue.gradient)
319
240
  }
320
241
  .chartYScale(domain: 0...1)
321
242
  .chartYAxis {
322
- AxisMarks(format: .percent)
243
+ AxisMarks { mark in
244
+ AxisValueLabel {
245
+ if let share = mark.as(Double.self) { Text(share, format: .percent) }
246
+ }
247
+ }
323
248
  }
324
249
  .chartXAxis {
325
250
  AxisMarks(values: .stride(by: .day)) { _ in
326
- AxisGridLine()
327
- AxisValueLabel(format: .dateTime.weekday(.abbreviated))
251
+ AxisValueLabel(format: .dateTime.weekday(.narrow), centered: true)
328
252
  }
329
253
  }
330
- .frame(height: 150)
331
254
  }
332
255
  }
333
256
  ```
334
257
 
335
- ## Historical Weather Statistics
258
+ ## Historical weather statistics
336
259
 
337
- WeatherKit provides historical weather data through daily and monthly statistics.
338
-
339
- ### Daily Statistics
260
+ iOS 18 added statistics that describe what is normal for a place, derived from
261
+ long-term records. The methods take variadic queries and return one result per
262
+ query.
340
263
 
341
264
  ```swift
342
- func fetchDailyStats(
343
- for location: CLLocation,
344
- dateRange: DateInterval
345
- ) async throws {
346
- let stats = try await WeatherService.shared.dailyStatistics(
347
- for: location,
348
- forDaysIn: dateRange,
349
- including: [.temperature, .precipitation]
265
+ @available(iOS 18.0, *)
266
+ func typicalJune(of year: Int, at place: CLLocation) async throws -> [(day: Int, low: String, high: String)] {
267
+ let calendar = Calendar(identifier: .gregorian)
268
+ let juneFirst = DateComponents(year: year, month: 6, day: 1)
269
+ let julyFirst = DateComponents(year: year, month: 7, day: 1)
270
+ guard let start = calendar.date(from: juneFirst), let end = calendar.date(from: julyFirst) else { return [] }
271
+
272
+ let service = WeatherService.shared
273
+ let (temperatures, precipitation) = try await service.dailyStatistics(
274
+ for: place,
275
+ forDaysIn: DateInterval(start: start, end: end),
276
+ including: .temperature, .precipitation
350
277
  )
278
+ _ = precipitation.days.map(\.averagePrecipitationProbability)
351
279
 
352
- for dayStat in stats {
353
- print("Date: \(dayStat.date)")
354
- if let temp = dayStat.statistics(for: .temperature) {
355
- print(" Avg temp: \(temp.mean?.formatted() ?? "N/A")")
356
- print(" Min temp: \(temp.minimum?.formatted() ?? "N/A")")
357
- print(" Max temp: \(temp.maximum?.formatted() ?? "N/A")")
358
- }
280
+ return temperatures.days.map { stats in
281
+ (stats.day, stats.averageLowTemperature.formatted(), stats.averageHighTemperature.formatted())
359
282
  }
360
283
  }
361
284
  ```
362
285
 
363
- ### Monthly Statistics
286
+ - `DailyWeatherStatistics<T>` is a collection; its `days` array holds
287
+ `DayTemperatureStatistics` (`day`, `averageLowTemperature`,
288
+ `averageHighTemperature`) or `DayPrecipitationStatistics` (`day`,
289
+ `averagePrecipitationProbability`, `averagePrecipitationAmount`,
290
+ `averageSnowfallAmount`). `day` is a day number, not a `Date`.
291
+ - `monthlyStatistics(for:including:)` returns `MonthlyWeatherStatistics<T>`
292
+ whose `months` array holds `MonthTemperatureStatistics` (`month`,
293
+ `averageLowTemperature`, `averageHighTemperature`) or the precipitation
294
+ equivalent.
295
+ - `hourlyStatistics(for:forHoursIn:including:)` and
296
+ `dailySummary(for:forDaysIn:including:)` follow the same shape for hourly
297
+ normals and for observed daily totals.
364
298
 
365
299
  ```swift
366
- func fetchMonthlyStats(for location: CLLocation) async throws {
367
- let stats = try await WeatherService.shared.monthlyStatistics(
368
- for: location,
369
- including: [.temperature, .precipitation]
370
- )
371
-
372
- for monthStat in stats {
373
- print("Month: \(monthStat.month)")
374
- }
300
+ @available(iOS 18.0, *)
301
+ func monthlyNormals(at place: CLLocation) async throws -> [Int: String] {
302
+ let service = WeatherService.shared
303
+ let normals = try await service.monthlyStatistics(for: place, including: .temperature)
304
+ return Dictionary(uniqueKeysWithValues: normals.months.map { ($0.month, $0.averageHighTemperature.formatted()) })
375
305
  }
376
306
  ```
377
307
 
378
- ## Weather Condition Mapping
308
+ ## Weather condition mapping
379
309
 
380
- ### Mapping Conditions to Colors
310
+ `WeatherCondition` has a readable `description`, but apps often want their own
311
+ icon or grouping. Switch over every case and keep `@unknown default` for cases
312
+ added later.
381
313
 
382
314
  ```swift
383
- extension WeatherCondition {
384
- var themeColor: Color {
385
- switch self {
386
- case .clear, .mostlyClear:
387
- return .yellow
388
- case .partlyCloudy, .mostlyCloudy, .cloudy:
389
- return .gray
390
- case .rain, .heavyRain, .drizzle:
391
- return .blue
392
- case .snow, .heavySnow, .flurries, .sleet, .freezingRain,
393
- .freezingDrizzle, .wintryMix, .blizzard:
394
- return .cyan
395
- case .thunderstorms, .strongStorms, .tropicalStorm, .hurricane:
396
- return .purple
397
- case .foggy, .haze, .smoky:
398
- return .gray.opacity(0.6)
399
- case .breezy, .windy:
400
- return .teal
401
- case .hot:
402
- return .red
403
- case .frigid, .blowingDust:
404
- return .indigo
405
- @unknown default:
406
- return .primary
407
- }
315
+ func skyGroup(_ condition: WeatherCondition) -> String {
316
+ switch condition {
317
+ case .clear, .mostlyClear, .hot:
318
+ return "sunny"
319
+ case .partlyCloudy, .mostlyCloudy, .cloudy:
320
+ return "cloudy"
321
+ case .drizzle, .rain, .heavyRain, .sunShowers:
322
+ return "rain"
323
+ case .snow, .heavySnow, .flurries, .sunFlurries, .blowingSnow, .blizzard:
324
+ return "snow"
325
+ case .sleet, .freezingRain, .freezingDrizzle, .wintryMix, .hail, .frigid:
326
+ return "ice"
327
+ case .thunderstorms, .isolatedThunderstorms, .scatteredThunderstorms,
328
+ .strongStorms, .tropicalStorm, .hurricane:
329
+ return "storm"
330
+ case .foggy, .haze, .smoky, .blowingDust:
331
+ return "low visibility"
332
+ case .breezy, .windy:
333
+ return "wind"
334
+ @unknown default:
335
+ return "unknown"
408
336
  }
409
337
  }
410
338
  ```
411
339
 
412
- ### Mapping Severity to Priority
340
+ `WeatherSeverity` orders alerts. Rank it explicitly:
413
341
 
414
342
  ```swift
415
- extension WeatherSeverity {
416
- var displayPriority: Int {
417
- switch self {
418
- case .extreme:
419
- return 4
420
- case .severe:
421
- return 3
422
- case .moderate:
423
- return 2
424
- case .minor:
425
- return 1
426
- case .unknown:
427
- return 0
428
- @unknown default:
429
- return 0
430
- }
343
+ func rank(_ severity: WeatherSeverity) -> Int {
344
+ switch severity {
345
+ case .extreme: 4
346
+ case .severe: 3
347
+ case .moderate: 2
348
+ case .minor: 1
349
+ case .unknown: 0
350
+ @unknown default: 0
431
351
  }
432
352
  }
433
353
  ```
434
354
 
435
- ## Caching Strategy
355
+ ## Caching strategy
436
356
 
437
- ### Actor-Based Weather Cache
357
+ An actor keeps recent results per rounded coordinate and throws them away once
358
+ they are older than the staleness window.
438
359
 
439
360
  ```swift
440
- actor WeatherCache {
441
- struct CacheEntry {
442
- let weather: CurrentWeather
443
- let hourly: Forecast<HourWeather>
444
- let daily: Forecast<DayWeather>
445
- let fetchDate: Date
361
+ actor ForecastCache {
362
+ struct Snapshot {
363
+ let now: CurrentWeather
364
+ let hours: Forecast<HourWeather>
365
+ let days: Forecast<DayWeather>
366
+ let fetchedAt: Date
446
367
  }
447
368
 
448
- private var cache: [String: CacheEntry] = [:]
449
- private let staleness: TimeInterval
369
+ private var store: [String: Snapshot] = [:]
370
+ private let maxAge: TimeInterval
371
+
372
+ init(maxAge: TimeInterval = 600) { self.maxAge = maxAge }
450
373
 
451
- init(staleness: TimeInterval = 600) { // 10 minutes default
452
- self.staleness = staleness
374
+ // Two decimal places is roughly a one-kilometre grid.
375
+ private func key(for place: CLLocation) -> String {
376
+ let spot = place.coordinate
377
+ return String(format: "%.2f|%.2f", spot.latitude, spot.longitude)
453
378
  }
454
379
 
455
- func get(for key: String) -> CacheEntry? {
456
- guard let entry = cache[key],
457
- Date.now.timeIntervalSince(entry.fetchDate) < staleness else {
458
- cache[key] = nil
380
+ func snapshot(for place: CLLocation) -> Snapshot? {
381
+ let slot = key(for: place)
382
+ guard let hit = store[slot] else { return nil }
383
+ if Date.now.timeIntervalSince(hit.fetchedAt) > maxAge {
384
+ store[slot] = nil
459
385
  return nil
460
386
  }
461
- return entry
387
+ return hit
462
388
  }
463
389
 
464
- func set(_ entry: CacheEntry, for key: String) {
465
- cache[key] = entry
466
- }
467
-
468
- /// Generate a cache key from a location (rounded to ~1km precision)
469
- static func key(for location: CLLocation) -> String {
470
- let lat = (location.coordinate.latitude * 100).rounded() / 100
471
- let lon = (location.coordinate.longitude * 100).rounded() / 100
472
- return "\(lat),\(lon)"
390
+ func keep(_ snapshot: Snapshot, for place: CLLocation) {
391
+ store[key(for: place)] = snapshot
473
392
  }
474
393
  }
475
- ```
476
394
 
477
- ### Using the Cache
478
-
479
- ```swift
480
- @Observable
481
- @MainActor
482
- final class CachedWeatherManager {
483
- private let service = WeatherService.shared
484
- private let cache = WeatherCache()
485
-
486
- var current: CurrentWeather?
487
-
488
- func fetchWeather(for location: CLLocation) async throws {
489
- let key = WeatherCache.key(for: location)
490
-
491
- if let cached = await cache.get(for: key) {
492
- current = cached.weather
493
- return
494
- }
495
-
496
- let (current, hourly, daily) = try await service.weather(
497
- for: location,
498
- including: .current, .hourly, .daily
499
- )
500
-
501
- let entry = WeatherCache.CacheEntry(
502
- weather: current,
503
- hourly: hourly,
504
- daily: daily,
505
- fetchDate: .now
506
- )
507
- await cache.set(entry, for: key)
508
- self.current = current
509
- }
395
+ func freshForecast(at place: CLLocation, cache: ForecastCache) async throws -> ForecastCache.Snapshot {
396
+ if let cached = await cache.snapshot(for: place) { return cached }
397
+ let service = WeatherService.shared
398
+ let (now, hours, days) = try await service.weather(for: place, including: .current, .hourly, .daily)
399
+ let snapshot = ForecastCache.Snapshot(now: now, hours: hours, days: days, fetchedAt: .now)
400
+ await cache.keep(snapshot, for: place)
401
+ return snapshot
510
402
  }
511
403
  ```
512
404
 
513
- ## Location-Based Weather
405
+ ## Location-based weather
514
406
 
515
- ### Combining CoreLocation with WeatherKit
407
+ A small location provider: ask for when-in-use permission, request one fix at
408
+ kilometre accuracy (plenty for weather), then fetch current conditions.
516
409
 
517
410
  ```swift
518
- import CoreLocation
519
- import WeatherKit
520
-
521
- @Observable
522
411
  @MainActor
523
- final class LocationWeatherManager: NSObject, CLLocationManagerDelegate {
524
- private let locationManager = CLLocationManager()
525
- private let weatherService = WeatherService.shared
526
-
527
- var current: CurrentWeather?
528
- var locationError: Error?
412
+ @Observable
413
+ final class LocalConditions: NSObject, CLLocationManagerDelegate {
414
+ private(set) var now: CurrentWeather?
415
+ private(set) var problem: String?
416
+ private let locator = CLLocationManager()
529
417
 
530
418
  override init() {
531
419
  super.init()
532
- locationManager.delegate = self
533
- locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
420
+ locator.delegate = self
421
+ locator.desiredAccuracy = kCLLocationAccuracyKilometer
534
422
  }
535
423
 
536
- func requestWeather() {
537
- locationManager.requestWhenInUseAuthorization()
538
- locationManager.requestLocation()
424
+ func start() {
425
+ locator.requestWhenInUseAuthorization()
426
+ locator.requestLocation()
539
427
  }
540
428
 
541
- nonisolated func locationManager(
542
- _ manager: CLLocationManager,
543
- didUpdateLocations locations: [CLLocation]
544
- ) {
545
- guard let location = locations.last else { return }
546
- Task { @MainActor in
547
- do {
548
- current = try await weatherService.weather(
549
- for: location,
550
- including: .current
551
- )
552
- } catch {
553
- locationError = error
554
- }
555
- }
429
+ nonisolated func locationManager(_ source: CLLocationManager, didUpdateLocations fixes: [CLLocation]) {
430
+ guard let latest = fixes.last else { return }
431
+ Task { @MainActor in await refresh(at: latest) }
556
432
  }
557
433
 
558
- nonisolated func locationManager(
559
- _ manager: CLLocationManager,
560
- didFailWithError error: Error
561
- ) {
562
- Task { @MainActor in
563
- locationError = error
434
+ private func refresh(at spot: CLLocation) async {
435
+ do {
436
+ let service = WeatherService.shared
437
+ now = try await service.weather(for: spot, including: .current)
438
+ } catch {
439
+ problem = error.localizedDescription
564
440
  }
565
441
  }
442
+
443
+ nonisolated func locationManager(_ source: CLLocationManager, didFailWithError error: any Error) {
444
+ let message = error.localizedDescription
445
+ Task { @MainActor in problem = message }
446
+ }
566
447
  }
567
448
  ```