@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,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
  ```