@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,683 +1,529 @@
1
- # HealthKit Extended Patterns
1
+ # HealthKit patterns
2
2
 
3
- Overflow reference for the `healthkit` skill. Contains advanced patterns that exceed the main skill file's scope.
3
+ Extended examples that go with the main skill. Swift 6.3, iOS 26 unless noted.
4
4
 
5
5
  ## Contents
6
6
 
7
- - [Workout Session Lifecycle](#workout-session-lifecycle)
8
- - [Platform and Authorization Edge Cases](#platform-and-authorization-edge-cases)
9
- - [Background Delivery Details](#background-delivery-details)
10
- - [Anchored Object Queries](#anchored-object-queries)
11
- - [Predicate-Based Filtering](#predicate-based-filtering)
12
- - [Statistics Collection for Charts](#statistics-collection-for-charts)
13
- - [HealthKit + SwiftUI Integration](#healthkit--swiftui-integration)
14
- - [Characteristic Types](#characteristic-types)
7
+ - [Workout sessions in depth](#workout-sessions-in-depth)
8
+ - [Platform and authorization edge cases](#platform-and-authorization-edge-cases)
9
+ - [Background delivery in detail](#background-delivery-in-detail)
10
+ - [Anchored object queries](#anchored-object-queries)
11
+ - [Predicates](#predicates)
12
+ - [Chart data](#chart-data)
13
+ - [SwiftUI integration](#swiftui-integration)
14
+ - [Characteristics](#characteristics)
15
15
 
16
- ## Workout Session Lifecycle
16
+ ## Workout sessions in depth
17
17
 
18
- `HKWorkoutSession` is available on iOS/iPadOS 17+, visionOS 1+, and watchOS
19
- 2+. `HKLiveWorkoutBuilder` is available on iOS/iPadOS 26+ and watchOS 5+.
20
- When supporting iOS or iPadOS earlier than 26, gate live-builder code and use a
21
- non-live workout save path where appropriate.
18
+ Availability repeats here because it drives the design: `HKWorkoutSession` is
19
+ iOS and iPadOS 17+, visionOS 1+ and watchOS 2+, while `HKLiveWorkoutBuilder`
20
+ is iOS and iPadOS 26+ and watchOS 5+. If the app still supports iOS or iPadOS
21
+ before 26, gate the live builder and fall back to building and saving the
22
+ workout after the fact instead of collecting it live.
22
23
 
23
- iPhone and iPad do not provide built-in live heart-rate samples. They require a
24
- paired external heart-rate sensor for live heart-rate collection, while Apple
25
- Watch workout sessions collect high-frequency heart-rate samples. iPhone also
26
- often locks during workouts; if the app shows health metrics on the Lock
27
- Screen, design around the system's workout-data access prompt and Live Activity
28
- surface.
24
+ iPhone and iPad produce no live heart-rate samples by themselves; an external
25
+ Bluetooth heart-rate sensor is needed. Phones also tend to lock partway through
26
+ a workout, so if metrics should show on the Lock Screen, design around the
27
+ system prompt that grants workout-data access there and around the Live
28
+ Activity that displays it.
29
29
 
30
- ### Full Workout Manager
30
+ ### A complete manager
31
31
 
32
32
  ```swift
33
33
  import HealthKit
34
+ import Observation
34
35
 
36
+ @available(iOS 26.0, *)
35
37
  @Observable
36
38
  @MainActor
37
- final class WorkoutManager: NSObject {
38
- let healthStore = HKHealthStore()
39
-
39
+ final class SwimTracker: NSObject {
40
+ private let store: HKHealthStore
40
41
  private var session: HKWorkoutSession?
41
42
  private var builder: HKLiveWorkoutBuilder?
42
43
 
43
- var heartRate: Double = 0
44
- var activeCalories: Double = 0
45
- var distance: Double = 0
46
- var elapsedTime: TimeInterval = 0
47
- var isActive = false
48
-
49
- func startWorkout(activityType: HKWorkoutActivityType) async throws {
50
- let configuration = HKWorkoutConfiguration()
51
- configuration.activityType = activityType
52
- configuration.locationType = .outdoor
53
-
54
- let session = try HKWorkoutSession(
55
- healthStore: healthStore,
56
- configuration: configuration
57
- )
58
- let builder = session.associatedWorkoutBuilder()
59
-
60
- builder.dataSource = HKLiveWorkoutDataSource(
61
- healthStore: healthStore,
62
- workoutConfiguration: configuration
63
- )
64
-
65
- session.delegate = self
66
- builder.delegate = self
67
-
68
- self.session = session
69
- self.builder = builder
44
+ private(set) var pulse: Double = 0
45
+ private(set) var kilocalories: Double = 0
46
+ private(set) var metres: Double = 0
47
+ private(set) var elapsed: TimeInterval = 0
48
+ private(set) var isRunning = false
70
49
 
71
- session.startActivity(with: Date())
72
- try await builder.beginCollection(at: Date())
73
- isActive = true
50
+ init(store: HKHealthStore) {
51
+ self.store = store
74
52
  }
75
53
 
76
- func pause() {
77
- session?.pause()
54
+ func start() async throws {
55
+ let plan = HKWorkoutConfiguration()
56
+ plan.activityType = .swimming
57
+ plan.swimmingLocationType = .pool
58
+ plan.lapLength = HKQuantity(unit: .meter(), doubleValue: 25)
59
+
60
+ let newSession = try HKWorkoutSession(healthStore: store, configuration: plan)
61
+ let newBuilder = newSession.associatedWorkoutBuilder()
62
+ newSession.delegate = self
63
+ newBuilder.delegate = self
64
+ newBuilder.dataSource = HKLiveWorkoutDataSource(healthStore: store, workoutConfiguration: plan)
65
+ session = newSession
66
+ builder = newBuilder
67
+
68
+ let startDate = Date()
69
+ newSession.startActivity(with: startDate)
70
+ try await newBuilder.beginCollection(at: startDate)
78
71
  }
79
72
 
80
- func resume() {
81
- session?.resume()
73
+ func pause() { session?.pause() }
74
+ func resume() { session?.resume() }
75
+
76
+ func stop() async throws {
77
+ guard let activeSession = session, let activeBuilder = builder else { return }
78
+ activeSession.end()
79
+ try await activeBuilder.endCollection(at: .now)
80
+ _ = try await activeBuilder.finishWorkout()
81
+ session = nil
82
+ builder = nil
82
83
  }
83
84
 
84
- func end() async throws {
85
- guard let session, let builder else { return }
86
- session.end()
87
- try await builder.endCollection(at: Date())
88
- try await builder.finishWorkout()
89
- isActive = false
90
- self.session = nil
91
- self.builder = nil
85
+ fileprivate func apply(_ stats: HKStatistics, for type: HKQuantityType) {
86
+ switch type {
87
+ case HKQuantityType(.heartRate):
88
+ pulse = stats.mostRecentQuantity()?.doubleValue(for: .count().unitDivided(by: .minute())) ?? pulse
89
+ case HKQuantityType(.activeEnergyBurned):
90
+ kilocalories = stats.sumQuantity()?.doubleValue(for: .kilocalorie()) ?? kilocalories
91
+ case HKQuantityType(.distanceSwimming):
92
+ metres = stats.sumQuantity()?.doubleValue(for: .meter()) ?? metres
93
+ default:
94
+ break
95
+ }
92
96
  }
93
97
  }
94
98
 
95
- // MARK: - HKWorkoutSessionDelegate
96
-
97
- extension WorkoutManager: HKWorkoutSessionDelegate {
98
- nonisolated func workoutSession(
99
- _ workoutSession: HKWorkoutSession,
100
- didChangeTo toState: HKWorkoutSessionState,
101
- from fromState: HKWorkoutSessionState,
102
- date: Date
103
- ) {
99
+ @available(iOS 26.0, *)
100
+ extension SwimTracker: HKWorkoutSessionDelegate {
101
+ nonisolated func workoutSession(_ sender: HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState,
102
+ from fromState: HKWorkoutSessionState, date: Date) {
104
103
  Task { @MainActor in
105
104
  switch toState {
106
- case .running:
107
- isActive = true
108
- case .paused:
109
- isActive = false
110
- case .ended, .stopped:
111
- isActive = false
112
- default:
113
- break
105
+ case .running: self.isRunning = true
106
+ case .paused, .ended, .stopped: self.isRunning = false
107
+ default: break
114
108
  }
115
109
  }
116
110
  }
117
111
 
118
- nonisolated func workoutSession(
119
- _ workoutSession: HKWorkoutSession,
120
- didFailWithError error: Error
121
- ) {
122
- Task { @MainActor in
123
- print("Workout session failed: \(error)")
124
- isActive = false
125
- }
112
+ nonisolated func workoutSession(_ sender: HKWorkoutSession, didFailWithError failure: Error) {
113
+ Task { @MainActor in self.isRunning = false }
126
114
  }
127
115
  }
128
116
 
129
- // MARK: - HKLiveWorkoutBuilderDelegate
130
-
131
- extension WorkoutManager: HKLiveWorkoutBuilderDelegate {
132
- nonisolated func workoutBuilderDidCollectEvent(
133
- _ workoutBuilder: HKLiveWorkoutBuilder
134
- ) {
135
- // Handle workout events (pause, resume, lap, etc.)
117
+ @available(iOS 26.0, *)
118
+ extension SwimTracker: HKLiveWorkoutBuilderDelegate {
119
+ nonisolated func workoutBuilderDidCollectEvent(_ workoutBuilder: HKLiveWorkoutBuilder) {
120
+ // Pause, resume and lap markers arrive here via workoutBuilder.workoutEvents.
136
121
  }
137
122
 
138
- nonisolated func workoutBuilder(
139
- _ workoutBuilder: HKLiveWorkoutBuilder,
140
- didCollectDataOf collectedTypes: Set<HKSampleType>
141
- ) {
123
+ nonisolated func workoutBuilder(_ workoutBuilder: HKLiveWorkoutBuilder, didCollectDataOf collectedTypes: Set<HKSampleType>) {
124
+ let time = workoutBuilder.elapsedTime
125
+ let snapshot = collectedTypes.compactMap { $0 as? HKQuantityType }
126
+ .compactMap { type in workoutBuilder.statistics(for: type).map { (type, $0) } }
142
127
  Task { @MainActor in
143
- for type in collectedTypes {
144
- guard let quantityType = type as? HKQuantityType else { continue }
128
+ self.elapsed = time
129
+ for (type, stats) in snapshot { self.apply(stats, for: type) }
130
+ }
131
+ }
132
+ }
133
+ ```
145
134
 
146
- let statistics = workoutBuilder.statistics(for: quantityType)
135
+ Points to notice:
147
136
 
148
- switch quantityType {
149
- case HKQuantityType(.heartRate):
150
- heartRate = statistics?.mostRecentQuantity()?
151
- .doubleValue(for: HKUnit.count().unitDivided(by: .minute())) ?? 0
137
+ - Both delegates are set: the session's for state changes and the builder's for
138
+ data.
139
+ - `pause()` and `resume()` go through the session.
140
+ - Ending runs `end()`, `endCollection(at:)`, `finishWorkout()` and then drops
141
+ the references.
142
+ - Delegate callbacks are `nonisolated` and hop back with
143
+ `Task { @MainActor in ... }`. Session states to handle include `.running`,
144
+ `.paused`, `.ended` and `.stopped`.
145
+ - In `didCollectDataOf`, cast each type to `HKQuantityType` and read
146
+ `statistics(for:)`: `mostRecentQuantity()` for heart rate in beats per
147
+ minute, `sumQuantity()` for energy in kilocalories and for distance in
148
+ metres. `elapsedTime` on the builder is the running clock.
152
149
 
153
- case HKQuantityType(.activeEnergyBurned):
154
- activeCalories = statistics?.sumQuantity()?
155
- .doubleValue(for: .kilocalorie()) ?? 0
150
+ ### Mirroring a watch workout to iPhone
156
151
 
157
- case HKQuantityType(.distanceWalkingRunning):
158
- distance = statistics?.sumQuantity()?
159
- .doubleValue(for: .meter()) ?? 0
152
+ The watch runs the primary session and starts mirroring:
160
153
 
161
- default:
162
- break
163
- }
164
- }
154
+ ```swift
155
+ import HealthKit
165
156
 
166
- elapsedTime = workoutBuilder.elapsedTime
167
- }
168
- }
157
+ #if os(watchOS)
158
+ func mirrorToPhone(_ primary: HKWorkoutSession) async throws {
159
+ try await primary.startMirroringToCompanionDevice()
169
160
  }
161
+ #endif
170
162
  ```
171
163
 
172
- ### Multi-Device Mirroring (watchOS + iOS)
173
-
174
- Start mirroring from the primary watchOS session. In the iOS companion app,
175
- assign `workoutSessionMirroringStartHandler` as the app launches so it can
176
- receive mirrored sessions even when launched in the background. The handler runs
177
- on an arbitrary background queue and may be called more than once if the devices
178
- disconnect and reconnect during a workout.
164
+ The iPhone app registers its handler at launch, early enough that a mirrored
165
+ session still arrives when the system launches the app in the background:
179
166
 
180
167
  ```swift
181
- // On watchOS: start mirroring to companion iPhone
182
- func startMirroring() async throws {
183
- try await session?.startMirroringToCompanionDevice()
184
- }
168
+ import HealthKit
185
169
 
186
- // On iOS: receive the mirrored session
187
- func setupMirroredSessionHandler() {
188
- healthStore.workoutSessionMirroringStartHandler = { mirroredSession in
189
- // mirroredSession is an HKWorkoutSession with type == .mirrored
190
- mirroredSession.delegate = self
191
- let builder = mirroredSession.associatedWorkoutBuilder()
192
- builder.delegate = self
170
+ @available(iOS 26.0, *)
171
+ final class CompanionReceiver: NSObject, Sendable, HKWorkoutSessionDelegate, HKLiveWorkoutBuilderDelegate {
172
+ let store = HKHealthStore()
173
+
174
+ func install() {
175
+ store.workoutSessionMirroringStartHandler = { [weak self] mirrored in
176
+ guard let self, mirrored.type == .mirrored else { return }
177
+ mirrored.delegate = self
178
+ mirrored.associatedWorkoutBuilder().delegate = self
179
+ }
193
180
  }
194
- }
195
181
 
196
- // Send data between devices
197
- func sendDataToRemote(_ data: Data) async throws {
198
- try await session?.sendToRemoteWorkoutSession(data: data)
182
+ func ping(_ session: HKWorkoutSession, payload: Data) async throws {
183
+ try await session.sendToRemoteWorkoutSession(data: payload)
184
+ }
185
+
186
+ func workoutSession(_ s: HKWorkoutSession, didChangeTo toState: HKWorkoutSessionState,
187
+ from fromState: HKWorkoutSessionState, date: Date) {}
188
+ func workoutSession(_ s: HKWorkoutSession, didFailWithError failure: Error) {}
189
+ func workoutSession(_ s: HKWorkoutSession, didReceiveDataFromRemoteWorkoutSession data: [Data]) {}
190
+ func workoutBuilderDidCollectEvent(_ workoutBuilder: HKLiveWorkoutBuilder) {}
191
+ func workoutBuilder(_ workoutBuilder: HKLiveWorkoutBuilder, didCollectDataOf collectedTypes: Set<HKSampleType>) {}
199
192
  }
200
193
  ```
201
194
 
202
- ## Platform and Authorization Edge Cases
203
-
204
- - Call `HKHealthStore.isHealthDataAvailable()` before any other HealthKit API.
205
- Current Apple docs say Health data is available on iOS, watchOS, visionOS,
206
- iPadOS 17+, and iOS apps running on Vision Pro. It is unavailable on iPadOS
207
- 16 or earlier and may be restricted by managed device policy.
208
- - Enabling the HealthKit capability for an iOS app can add `healthkit` to
209
- `UIRequiredDeviceCapabilities`, preventing installation on unsupported
210
- devices. Remove that entry only when HealthKit is optional and the app has a
211
- useful non-HealthKit mode.
212
- - `authorizationStatus(for:)` is for write/share authorization. HealthKit does
213
- not reveal whether read access was granted or denied. If read access is
214
- denied, queries return samples your app saved successfully, which can look
215
- like partial or empty data.
216
- - People can change HealthKit permissions later in Settings or the Health app.
217
- Refresh permission-sensitive UI and write paths instead of assuming the
218
- original authorization outcome still applies.
219
- - In Vision Pro Guest User sessions, previously authorized data may be readable,
220
- but new authorization and writes can fail. Treat HealthKit writes as
221
- best-effort unless the user explicitly initiated a save action that needs an
222
- explanation.
223
-
224
- ## Background Delivery Details
225
-
226
- Background delivery requires the HealthKit Background Delivery capability
227
- (`com.apple.developer.healthkit.background-delivery` on current Apple docs) and
228
- an executed `HKObserverQuery` for the same sample type. Set up observer queries
229
- when the app launches, then call `enableBackgroundDelivery` once; the system
230
- persists the registration.
195
+ - The handler is called on an arbitrary background queue.
196
+ - It can fire more than once, for example after the devices disconnect and
197
+ reconnect, so make it idempotent.
198
+ - The session it receives is an ordinary `HKWorkoutSession` whose `type` is
199
+ `.mirrored`. Set the session delegate and the delegate of its
200
+ `associatedWorkoutBuilder()`.
201
+ - Either side sends custom data with `sendToRemoteWorkoutSession(data:)`.
202
+
203
+ ## Platform and authorization edge cases
204
+
205
+ - Turning on the capability can add `healthkit` to
206
+ `UIRequiredDeviceCapabilities`, which stops the app from installing on
207
+ devices without Health. Remove the entry only when HealthKit really is
208
+ optional and the app offers something useful without it.
209
+ - People change permissions later in Settings or in the Health app. Re-check
210
+ before permission-dependent UI and write paths instead of trusting the answer
211
+ from the first request.
212
+ - In a Guest User session on Apple Vision Pro, data the owner already allowed
213
+ may be readable while new authorization requests and writes fail. Treat
214
+ writes as best-effort there, unless the user explicitly asked to save, in
215
+ which case explain what happened.
216
+
217
+ ## Background delivery in detail
218
+
219
+ - Entitlement: `com.apple.developer.healthkit.background-delivery`, added by
220
+ the HealthKit Background Delivery capability option.
221
+ - An executed `HKObserverQuery` for the same sample type must exist.
222
+ - `HKUpdateFrequency` is an upper bound, not a promise.
223
+ - On iOS, step count is delivered at most hourly even if `.immediate` is
224
+ requested.
225
+ - Background server queries do not run in Simulator.
226
+ - If the completion handler is not called, HealthKit backs off and, after
227
+ repeated misses, may stop delivering.
231
228
 
232
229
  ```swift
233
- func configureHealthKitBackgroundDelivery() async throws {
234
- let stepType = HKQuantityType(.stepCount)
230
+ import HealthKit
235
231
 
236
- let query = HKObserverQuery(
237
- sampleType: stepType,
238
- predicate: nil
239
- ) { _, completionHandler, error in
232
+ func startSleepSync(store: HKHealthStore, sync: @escaping @Sendable () async -> Void) async throws {
233
+ let sleep = HKCategoryType(.sleepAnalysis)
234
+ let observer = HKObserverQuery(sampleType: sleep, predicate: nil) { _, completionHandler, error in
240
235
  defer { completionHandler() }
241
- guard error == nil else { return }
242
- Task {
243
- // Run an anchored query or statistics refresh here.
244
- }
236
+ if error != nil { return }
237
+ Task { await sync() }
245
238
  }
246
-
247
- healthStore.execute(query)
248
-
249
- try await healthStore.enableBackgroundDelivery(
250
- for: stepType,
251
- frequency: .hourly
252
- )
239
+ store.execute(observer)
240
+ try await store.enableBackgroundDelivery(for: sleep, frequency: .hourly)
253
241
  }
254
242
  ```
255
243
 
256
- Treat `HKUpdateFrequency` as a maximum delivery rate, not a guarantee. Some
257
- types have tighter system caps; for example, iOS step-count background delivery
258
- is capped at hourly even if `.immediate` is requested. Background server queries
259
- are not supported on Simulator, so validate delivery on real hardware. If the
260
- observer completion handler is not called, HealthKit backs off and can stop
261
- sending background updates after repeated failures.
244
+ The `sync` closure is where an anchored query or a statistics refresh runs.
262
245
 
263
- ## Anchored Object Queries
246
+ ## Anchored object queries
264
247
 
265
- Use `HKAnchoredObjectQuery` for incremental updates -- only fetches samples added or deleted since the last anchor.
248
+ `HKAnchoredObjectQuery` returns only what changed (added or deleted) since the
249
+ last `HKQueryAnchor`.
266
250
 
267
251
  ```swift
268
- @Observable
269
- final class StepTracker {
270
- private let healthStore = HKHealthStore()
271
- private var anchor: HKQueryAnchor?
272
- private var observerQuery: HKObserverQuery?
252
+ import HealthKit
253
+ import Synchronization
273
254
 
274
- var totalSteps: Double = 0
255
+ final class GlucoseFeed: Sendable {
256
+ private let store: HKHealthStore
257
+ private let anchor = Mutex<HKQueryAnchor?>(nil)
275
258
 
276
- func startMonitoring() {
277
- let stepType = HKQuantityType(.stepCount)
259
+ init(store: HKHealthStore) { self.store = store }
278
260
 
279
- // Initial fetch + ongoing updates
280
- let anchoredQuery = HKAnchoredObjectQuery(
281
- type: stepType,
261
+ func start(onChange: @escaping @Sendable ([HKSample], [HKDeletedObject]) -> Void) {
262
+ let query = HKAnchoredObjectQuery(
263
+ type: HKQuantityType(.bloodGlucose),
282
264
  predicate: nil,
283
- anchor: anchor,
265
+ anchor: anchor.withLock { $0 },
284
266
  limit: HKObjectQueryNoLimit
285
- ) { [weak self] query, added, deleted, newAnchor, error in
286
- guard let self else { return }
287
- self.anchor = newAnchor
288
- self.processNewSamples(added ?? [])
267
+ ) { [weak self] _, added, removed, nextAnchor, _ in
268
+ self?.anchor.withLock { $0 = nextAnchor }
269
+ onChange(added ?? [], removed ?? [])
289
270
  }
290
-
291
- // Enable updates handler for real-time monitoring
292
- anchoredQuery.updateHandler = { [weak self] query, added, deleted, newAnchor, error in
293
- guard let self else { return }
294
- self.anchor = newAnchor
295
- self.processNewSamples(added ?? [])
296
- }
297
-
298
- healthStore.execute(anchoredQuery)
299
- }
300
-
301
- private func processNewSamples(_ samples: [HKSample]) {
302
- for sample in samples {
303
- guard let quantitySample = sample as? HKQuantitySample else { continue }
304
- let steps = quantitySample.quantity.doubleValue(for: .count())
305
- totalSteps += steps
271
+ query.updateHandler = { [weak self] _, added, removed, nextAnchor, _ in
272
+ self?.anchor.withLock { $0 = nextAnchor }
273
+ onChange(added ?? [], removed ?? [])
306
274
  }
275
+ store.execute(query)
307
276
  }
308
277
  }
309
- ```
310
278
 
311
- ### Anchored Query with Async Descriptor
312
-
313
- ```swift
314
- import HealthKit
315
-
316
- let stepType = HKQuantityType(.stepCount)
317
- let descriptor = HKAnchoredObjectQueryDescriptor(
318
- predicates: [.quantitySample(type: stepType)],
319
- anchor: savedAnchor
320
- )
321
-
322
- // One-shot
323
- let result = try await descriptor.result(for: healthStore)
324
- let newSamples = result.addedSamples
325
- let deletedObjects = result.deletedObjects
326
- let newAnchor = result.newAnchor
327
-
328
- // Long-running with updates
329
- for try await result in descriptor.results(for: healthStore) {
330
- // Process result.addedSamples and result.deletedObjects
279
+ func glucoseChanges(since mark: HKQueryAnchor?, store: HKHealthStore) async throws -> HKQueryAnchor {
280
+ let query = HKAnchoredObjectQueryDescriptor(
281
+ predicates: [.quantitySample(type: HKQuantityType(.bloodGlucose))],
282
+ anchor: mark
283
+ )
284
+ let delta = try await query.result(for: store)
285
+ _ = (delta.addedSamples, delta.deletedObjects)
286
+ return delta.newAnchor
331
287
  }
332
288
  ```
333
289
 
334
- ## Predicate-Based Filtering
290
+ The first handler delivers the initial batch; `updateHandler` keeps delivering
291
+ changes. Both run on a background queue, so the saved anchor sits behind a
292
+ `Mutex`. With the descriptor, `result(for:)` returns `addedSamples`,
293
+ `deletedObjects` and `newAnchor`, and `results(for:)` streams further updates.
335
294
 
336
- ### Time-Based Predicates
295
+ ## Predicates
337
296
 
338
297
  ```swift
339
- // Samples from today
340
- let today = Calendar.current.startOfDay(for: Date())
341
- let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: today)!
342
- let todayPredicate = HKQuery.predicateForSamples(
343
- withStart: today, end: tomorrow
344
- )
345
-
346
- // Samples from the last 7 days
347
- let oneWeekAgo = Calendar.current.date(byAdding: .day, value: -7, to: Date())!
348
- let weekPredicate = HKQuery.predicateForSamples(
349
- withStart: oneWeekAgo, end: Date()
350
- )
351
-
352
- // Strict: sample must be entirely within the range
353
- let strictPredicate = HKQuery.predicateForSamples(
354
- withStart: today, end: tomorrow,
355
- options: .strictStartDate
356
- )
357
- ```
298
+ import Foundation
299
+ import HealthKit
358
300
 
359
- ### Source-Based Predicates
301
+ func predicates() -> NSPredicate? {
302
+ let now = Date()
303
+ let calendar = Calendar.current
304
+ let todayStart = calendar.startOfDay(for: now)
305
+ guard let weekAgo = calendar.date(byAdding: .day, value: -7, to: now) else { return nil }
360
306
 
361
- ```swift
362
- // Only samples from the current app
363
- let sourcePredicate = HKQuery.predicateForObjects(
364
- from: HKSource.default()
365
- )
366
-
367
- // Only samples from Apple Watch
368
- let devicePredicate = HKQuery.predicateForObjects(
369
- withDeviceProperty: HKDevicePropertyKeyModel,
370
- allowedValues: ["Watch"]
371
- )
372
- ```
307
+ let today = HKQuery.predicateForSamples(withStart: todayStart, end: now)
308
+ let lastWeek = HKQuery.predicateForSamples(withStart: weekAgo, end: now)
309
+ let startsInWeek = HKQuery.predicateForSamples(withStart: weekAgo, end: now, options: .strictStartDate)
310
+ let mine = HKQuery.predicateForObjects(from: HKSource.default())
311
+ let fromWatch = HKQuery.predicateForObjects(withDeviceProperty: HKDevicePropertyKeyModel, allowedValues: ["Watch"])
373
312
 
374
- ### Compound Predicates
313
+ _ = (today, lastWeek)
314
+ return NSCompoundPredicate(andPredicateWithSubpredicates: [startsInWeek, mine, fromWatch])
315
+ }
375
316
 
376
- ```swift
377
- let todayFromWatch = NSCompoundPredicate(
378
- andPredicateWithSubpredicates: [todayPredicate, devicePredicate]
379
- )
380
-
381
- let descriptor = HKSampleQueryDescriptor(
382
- predicates: [.quantitySample(type: stepType, predicate: todayFromWatch)],
383
- sortDescriptors: [SortDescriptor(\.endDate, order: .reverse)]
384
- )
317
+ func watchStepsThisWeek(_ filter: NSPredicate) -> HKSampleQueryDescriptor<HKQuantitySample> {
318
+ HKSampleQueryDescriptor(
319
+ predicates: [.quantitySample(type: HKQuantityType(.stepCount), predicate: filter)],
320
+ sortDescriptors: []
321
+ )
322
+ }
385
323
  ```
386
324
 
387
- ## Statistics Collection for Charts
325
+ - `predicateForSamples(withStart:end:)` filters by time range.
326
+ - Adding `options: .strictStartDate` keeps only samples whose start date falls
327
+ inside the range.
328
+ - `predicateForObjects(from: HKSource.default())` keeps this app's own samples.
329
+ - `predicateForObjects(withDeviceProperty:allowedValues:)` filters by device,
330
+ here the model `"Watch"`.
331
+ - `NSCompoundPredicate(andPredicateWithSubpredicates:)` combines them before
332
+ they go into `.quantitySample(type:predicate:)`.
388
333
 
389
- ### Weekly Step Count Chart Data
334
+ ## Chart data
390
335
 
391
336
  ```swift
392
- struct DailyStepData: Identifiable {
393
- let id = UUID()
394
- let date: Date
395
- let steps: Double
337
+ import HealthKit
338
+
339
+ struct DayTotal: Identifiable {
340
+ let day: Date
341
+ let value: Double
342
+ var id: Date { day }
396
343
  }
397
344
 
398
- func fetchWeeklyStepData() async throws -> [DailyStepData] {
345
+ func lastSevenDaysOfSteps(_ store: HKHealthStore) async throws -> [DayTotal] {
399
346
  let calendar = Calendar.current
400
- let today = calendar.startOfDay(for: Date())
401
- let endDate = calendar.date(byAdding: .day, value: 1, to: today)!
402
- let startDate = calendar.date(byAdding: .day, value: -7, to: endDate)!
403
-
404
- let stepType = HKQuantityType(.stepCount)
405
- let predicate = HKQuery.predicateForSamples(
406
- withStart: startDate, end: endDate
407
- )
408
- let samplePredicate = HKSamplePredicate.quantitySample(
409
- type: stepType, predicate: predicate
410
- )
411
-
347
+ guard let end = calendar.date(byAdding: .day, value: 1, to: calendar.startOfDay(for: .now)),
348
+ let start = calendar.date(byAdding: .day, value: -7, to: end) else { return [] }
412
349
  let query = HKStatisticsCollectionQueryDescriptor(
413
- predicate: samplePredicate,
350
+ predicate: .quantitySample(type: HKQuantityType(.stepCount),
351
+ predicate: HKQuery.predicateForSamples(withStart: start, end: end)),
414
352
  options: .cumulativeSum,
415
- anchorDate: endDate,
353
+ anchorDate: end,
416
354
  intervalComponents: DateComponents(day: 1)
417
355
  )
418
-
419
- let collection = try await query.result(for: healthStore)
420
-
421
- var data: [DailyStepData] = []
422
- collection.statisticsCollection.enumerateStatistics(
423
- from: startDate, to: endDate
424
- ) { statistics, _ in
425
- let steps = statistics.sumQuantity()?
426
- .doubleValue(for: .count()) ?? 0
427
- data.append(DailyStepData(date: statistics.startDate, steps: steps))
356
+ var totals: [DayTotal] = []
357
+ try await query.result(for: store).enumerateStatistics(from: start, to: end) { stats, _ in
358
+ totals.append(DayTotal(day: stats.startDate, value: stats.sumQuantity()?.doubleValue(for: .count()) ?? 0))
428
359
  }
429
-
430
- return data
360
+ return totals
431
361
  }
432
- ```
433
-
434
- ### Hourly Heart Rate Averages
435
-
436
- ```swift
437
- func fetchHourlyHeartRate(for date: Date) async throws -> [(hour: Date, bpm: Double)] {
438
- let calendar = Calendar.current
439
- let startOfDay = calendar.startOfDay(for: date)
440
- let endOfDay = calendar.date(byAdding: .day, value: 1, to: startOfDay)!
441
-
442
- let heartRateType = HKQuantityType(.heartRate)
443
- let predicate = HKQuery.predicateForSamples(
444
- withStart: startOfDay, end: endOfDay
445
- )
446
- let samplePredicate = HKSamplePredicate.quantitySample(
447
- type: heartRateType, predicate: predicate
448
- )
449
362
 
363
+ func hourlyPulse(_ store: HKHealthStore, on day: Date) async throws -> [DayTotal] {
364
+ let start = Calendar.current.startOfDay(for: day)
365
+ let end = start.addingTimeInterval(24 * 3_600)
450
366
  let query = HKStatisticsCollectionQueryDescriptor(
451
- predicate: samplePredicate,
367
+ predicate: .quantitySample(type: HKQuantityType(.heartRate),
368
+ predicate: HKQuery.predicateForSamples(withStart: start, end: end)),
452
369
  options: .discreteAverage,
453
- anchorDate: endOfDay,
370
+ anchorDate: start,
454
371
  intervalComponents: DateComponents(hour: 1)
455
372
  )
456
-
457
- let collection = try await query.result(for: healthStore)
458
- let unit = HKUnit.count().unitDivided(by: .minute())
459
-
460
- var hourlyData: [(hour: Date, bpm: Double)] = []
461
- collection.statisticsCollection.enumerateStatistics(
462
- from: startOfDay, to: endOfDay
463
- ) { statistics, _ in
464
- if let avg = statistics.averageQuantity()?.doubleValue(for: unit) {
465
- hourlyData.append((hour: statistics.startDate, bpm: avg))
373
+ let bpm = HKUnit.count().unitDivided(by: .minute())
374
+ var points: [DayTotal] = []
375
+ try await query.result(for: store).enumerateStatistics(from: start, to: end) { stats, _ in
376
+ if let average = stats.averageQuantity()?.doubleValue(for: bpm) {
377
+ points.append(DayTotal(day: stats.startDate, value: average))
466
378
  }
467
379
  }
468
-
469
- return hourlyData
380
+ return points
470
381
  }
471
382
  ```
472
383
 
473
- ## HealthKit + SwiftUI Integration
384
+ The weekly chart covers the seven days ending at the start of tomorrow and
385
+ anchors on that end date. The hourly chart uses `.discreteAverage` and adds a
386
+ point only for hours that have data.
474
387
 
475
- ### HealthKit Manager with `@Observable`
388
+ ## SwiftUI integration
476
389
 
477
390
  ```swift
478
391
  import HealthKit
392
+ import Observation
479
393
  import SwiftUI
480
394
 
481
395
  @Observable
482
396
  @MainActor
483
- final class HealthManager {
484
- let healthStore = HKHealthStore()
485
-
486
- var isAuthorized = false
487
- var todaySteps: Double = 0
488
- var recentHeartRate: Double = 0
489
-
490
- var isAvailable: Bool {
491
- HKHealthStore.isHealthDataAvailable()
492
- }
493
-
494
- func requestAuthorization() async throws {
495
- let typesToRead: Set<HKObjectType> = [
496
- HKQuantityType(.stepCount),
497
- HKQuantityType(.heartRate)
498
- ]
499
-
500
- try await healthStore.requestAuthorization(
501
- toShare: [],
502
- read: typesToRead
503
- )
504
- isAuthorized = true
397
+ final class VitalsModel {
398
+ private let store = HKHealthStore()
399
+ var isAvailable: Bool { HKHealthStore.isHealthDataAvailable() }
400
+ private(set) var isAuthorized = false
401
+ private(set) var steps: Double = 0
402
+ private(set) var restingPulse: Double?
403
+
404
+ func authorize() async {
405
+ do {
406
+ try await store.requestAuthorization(toShare: [], read: [
407
+ HKQuantityType(.stepCount), HKQuantityType(.restingHeartRate)
408
+ ])
409
+ isAuthorized = true
410
+ } catch {
411
+ isAuthorized = false
412
+ }
505
413
  }
506
414
 
507
- func refreshData() async {
508
- async let steps = fetchTodaySteps()
509
- async let heartRate = fetchLatestHeartRate()
510
-
511
- todaySteps = (try? await steps) ?? 0
512
- recentHeartRate = (try? await heartRate) ?? 0
415
+ func refresh() async {
416
+ async let stepTotal = todaysSteps()
417
+ async let pulse = latestRestingPulse()
418
+ steps = (try? await stepTotal) ?? 0
419
+ restingPulse = try? await pulse
513
420
  }
514
421
 
515
- private func fetchTodaySteps() async throws -> Double {
516
- let calendar = Calendar.current
517
- let startOfDay = calendar.startOfDay(for: Date())
518
- let predicate = HKQuery.predicateForSamples(
519
- withStart: startOfDay, end: Date()
520
- )
521
- let stepType = HKQuantityType(.stepCount)
522
- let samplePredicate = HKSamplePredicate.quantitySample(
523
- type: stepType, predicate: predicate
524
- )
422
+ private func todaysSteps() async throws -> Double {
423
+ let start = Calendar.current.startOfDay(for: .now)
525
424
  let query = HKStatisticsQueryDescriptor(
526
- predicate: samplePredicate, options: .cumulativeSum
425
+ predicate: .quantitySample(type: HKQuantityType(.stepCount),
426
+ predicate: HKQuery.predicateForSamples(withStart: start, end: .now)),
427
+ options: .cumulativeSum
527
428
  )
528
- return try await query.result(for: healthStore)?
529
- .sumQuantity()?.doubleValue(for: .count()) ?? 0
429
+ return try await query.result(for: store)?.sumQuantity()?.doubleValue(for: .count()) ?? 0
530
430
  }
531
431
 
532
- private func fetchLatestHeartRate() async throws -> Double {
533
- let heartRateType = HKQuantityType(.heartRate)
534
- let descriptor = HKSampleQueryDescriptor(
535
- predicates: [.quantitySample(type: heartRateType)],
432
+ private func latestRestingPulse() async throws -> Double? {
433
+ let query = HKSampleQueryDescriptor(
434
+ predicates: [.quantitySample(type: HKQuantityType(.restingHeartRate))],
536
435
  sortDescriptors: [SortDescriptor(\.endDate, order: .reverse)],
537
436
  limit: 1
538
437
  )
539
- let results = try await descriptor.result(for: healthStore)
540
- let unit = HKUnit.count().unitDivided(by: .minute())
541
- return results.first?.quantity.doubleValue(for: unit) ?? 0
438
+ return try await query.result(for: store).first?.quantity
439
+ .doubleValue(for: .count().unitDivided(by: .minute()))
542
440
  }
543
441
  }
544
- ```
545
442
 
546
- ### SwiftUI View with HealthKit
547
-
548
- ```swift
549
- struct HealthDashboardView: View {
550
- @Environment(HealthManager.self) private var healthManager
443
+ struct VitalsScreen: View {
444
+ @Environment(VitalsModel.self) private var model
551
445
 
552
446
  var body: some View {
553
- NavigationStack {
554
- Group {
555
- if !healthManager.isAvailable {
556
- ContentUnavailableView(
557
- "HealthKit Unavailable",
558
- systemImage: "heart.slash",
559
- description: Text("This device does not support HealthKit.")
560
- )
561
- } else if !healthManager.isAuthorized {
562
- authorizationPrompt
563
- } else {
564
- healthDataView
565
- }
566
- }
567
- .navigationTitle("Health")
568
- }
569
- }
570
-
571
- private var authorizationPrompt: some View {
572
- ContentUnavailableView {
573
- Label("Health Access", systemImage: "heart.text.square")
574
- } description: {
575
- Text("Grant access to view your health data.")
576
- } actions: {
577
- Button("Authorize") {
578
- Task {
579
- try? await healthManager.requestAuthorization()
580
- }
447
+ if !model.isAvailable {
448
+ ContentUnavailableView("Health data unavailable", systemImage: "heart.slash")
449
+ } else if !model.isAuthorized {
450
+ ContentUnavailableView {
451
+ Label("Connect to Health", systemImage: "heart.text.square")
452
+ } actions: {
453
+ Button("Allow access") { Task { await model.authorize() } }
581
454
  }
582
- .buttonStyle(.borderedProminent)
583
- }
584
- }
585
-
586
- private var healthDataView: some View {
587
- List {
588
- Section("Today") {
589
- LabeledContent("Steps") {
590
- Text(healthManager.todaySteps, format: .number.precision(.fractionLength(0)))
591
- }
592
- LabeledContent("Heart Rate") {
593
- Text("\(Int(healthManager.recentHeartRate)) bpm")
594
- }
455
+ } else {
456
+ List {
457
+ LabeledContent("Steps today", value: model.steps, format: .number)
458
+ LabeledContent("Resting pulse", value: model.restingPulse.map { "\(Int($0)) bpm" } ?? "-")
595
459
  }
596
- }
597
- .task {
598
- await healthManager.refreshData()
599
- }
600
- .refreshable {
601
- await healthManager.refreshData()
460
+ .task { await model.refresh() }
461
+ .refreshable { await model.refresh() }
602
462
  }
603
463
  }
604
464
  }
605
- ```
606
465
 
607
- ### App Entry Point Wiring
608
-
609
- ```swift
610
- @main
611
- struct MyHealthApp: App {
612
- @State private var healthManager = HealthManager()
466
+ struct VitalsApp: App {
467
+ @State private var model = VitalsModel()
613
468
 
614
469
  var body: some Scene {
615
470
  WindowGroup {
616
- HealthDashboardView()
617
- .environment(healthManager)
471
+ VitalsScreen().environment(model)
618
472
  }
619
473
  }
620
474
  }
621
475
  ```
622
476
 
623
- ## Characteristic Types
477
+ - The model requests read-only access by passing an empty `toShare` set.
478
+ - `refresh()` runs both fetches at once with `async let`.
479
+ - The latest value comes from a sample descriptor with `limit: 1` sorted by end
480
+ date, newest first.
481
+ - The screen checks availability first, then authorization, then shows the
482
+ data. `isAuthorized` becomes `true` once `requestAuthorization` returns
483
+ without throwing, which only means the sheet was handled, not that reads were
484
+ allowed.
485
+ - The app creates the model as `@State` and injects it with `.environment`.
624
486
 
625
- Characteristic types are read-only values set by the user in the Health app. They do not require sample queries.
487
+ ## Characteristics
488
+
489
+ Characteristics are values the user enters once in Health, such as blood type.
490
+ They are read directly; no query is involved, and all of these calls throw.
626
491
 
627
492
  ```swift
628
- func readCharacteristics() throws {
629
- // Date of birth
630
- let dobComponents = try healthStore.dateOfBirthComponents()
631
- let calendar = Calendar.current
632
- if let dob = calendar.date(from: dobComponents) {
633
- let age = calendar.dateComponents([.year], from: dob, to: Date()).year ?? 0
634
- print("Age: \(age)")
635
- }
493
+ import HealthKit
636
494
 
637
- // Biological sex
638
- let biologicalSex = try healthStore.biologicalSex().biologicalSex
639
- switch biologicalSex {
640
- case .female: print("Female")
641
- case .male: print("Male")
642
- case .other: print("Other")
643
- case .notSet: print("Not set")
644
- @unknown default: break
645
- }
495
+ struct Profile {
496
+ var age: Int?
497
+ var sex: HKBiologicalSex = .notSet
498
+ var blood: HKBloodType = .notSet
499
+ var skin: HKFitzpatrickSkinType = .notSet
500
+ var wheelchair: HKWheelchairUse = .notSet
501
+ }
646
502
 
647
- // Blood type
648
- let bloodType = try healthStore.bloodType().bloodType
649
- switch bloodType {
650
- case .aPositive: print("A+")
651
- case .aNegative: print("A-")
652
- case .bPositive: print("B+")
653
- case .bNegative: print("B-")
654
- case .abPositive: print("AB+")
655
- case .abNegative: print("AB-")
656
- case .oPositive: print("O+")
657
- case .oNegative: print("O-")
658
- case .notSet: print("Not set")
659
- @unknown default: break
503
+ func loadProfile(_ store: HKHealthStore) -> Profile {
504
+ var profile = Profile()
505
+ do {
506
+ let born = try store.dateOfBirthComponents()
507
+ if let birthday = Calendar.current.date(from: born) {
508
+ profile.age = Calendar.current.dateComponents([.year], from: birthday, to: .now).year
509
+ }
510
+ } catch let error as HKError where error.code == .errorNoData {
511
+ profile.age = nil
512
+ } catch {
513
+ profile.age = nil
660
514
  }
661
-
662
- // Fitzpatrick skin type
663
- let skinType = try healthStore.fitzpatrickSkinType().skinType
664
- print("Skin type: \(skinType.rawValue)")
665
-
666
- // Wheelchair use
667
- let wheelchair = try healthStore.wheelchairUse().wheelchairUse
668
- print("Wheelchair: \(wheelchair == .yes)")
515
+ profile.sex = (try? store.biologicalSex().biologicalSex) ?? .notSet
516
+ profile.blood = (try? store.bloodType().bloodType) ?? .notSet
517
+ profile.skin = (try? store.fitzpatrickSkinType().skinType) ?? .notSet
518
+ profile.wheelchair = (try? store.wheelchairUse().wheelchairUse) ?? .notSet
519
+ return profile
669
520
  }
670
521
  ```
671
522
 
672
- **Important:** These throw an error if the value is not set, so always use `try` and handle the `HKError.errorNoData` case.
523
+ | Type | Cases |
524
+ |---|---|
525
+ | `HKBiologicalSex` | `.female`, `.male`, `.other`, `.notSet` |
526
+ | `HKBloodType` | `.aPositive`, `.aNegative`, `.bPositive`, `.bNegative`, `.abPositive`, `.abNegative`, `.oPositive`, `.oNegative`, `.notSet` |
673
527
 
674
- ```swift
675
- do {
676
- let dob = try healthStore.dateOfBirthComponents()
677
- // Use dob
678
- } catch let error as HKError where error.code == .errorNoData {
679
- // User hasn't set date of birth in Health app
680
- } catch {
681
- // Other error
682
- }
683
- ```
528
+ An unset value throws `HKError` with code `.errorNoData`; catch that case
529
+ separately from real failures.