@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +0 -10
  3. package/docs/facts.json +1 -1
  4. package/manifest.json +266 -267
  5. package/package.json +2 -2
  6. package/pipeline/scripts/_notices.mjs +1 -1
  7. package/pipeline/skills/.skill-manifest.json +68 -68
  8. package/pipeline/skills/shared/README.md +70 -70
  9. package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
  10. package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
  11. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
  12. package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
  13. package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
  14. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
  15. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
  16. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
  17. package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
  18. package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
  19. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
  20. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
  21. package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
  22. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
  23. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
  24. package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
  25. package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
  26. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
  27. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
  28. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
  29. package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
  30. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
  31. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
  32. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
  33. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
  34. package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
  35. package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
  36. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
  37. package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
  38. package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
  39. package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
  40. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
  41. package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
  42. package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
  43. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
  44. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
  45. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
  46. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
  47. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
  48. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
  49. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
  50. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
  51. package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
  52. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
  53. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
  54. package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
  55. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
  56. package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
  57. package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
  58. package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
  59. package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
  60. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
  61. package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
  62. package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
  63. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
  64. package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
  65. package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
  66. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
  67. package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
  68. package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
  69. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
  70. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
  71. package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
  72. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
  73. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
  74. package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
  75. package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
  76. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
  77. package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
  78. package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
  79. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
  80. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
  81. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
  82. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
  83. package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
  84. package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
  85. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
  86. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
  87. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
  88. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
  89. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
  90. package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
  91. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
  92. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
  93. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
  94. package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
  95. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
  96. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
  97. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
  98. package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
  99. package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
  100. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
  101. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
  102. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
  103. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
  104. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
  105. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
  106. package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
  107. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
  108. package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
  109. package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
  110. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
  111. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
  112. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
  113. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
  114. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
  115. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
  116. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
  117. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
  118. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
  119. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
  120. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
  121. package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
  122. package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
  123. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
  124. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
  125. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
  126. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
  127. package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
  128. package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
  129. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
  130. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
  131. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
  132. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
  133. package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
  134. package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
  135. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
  136. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
  137. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
  138. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
  139. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
  140. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
  141. package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
  142. package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
  143. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
  144. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
  145. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
  146. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
  147. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
  148. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
  149. package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
  150. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
  151. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
  152. package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
  153. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
  154. package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
  155. package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
  156. package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
  157. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
  158. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
  159. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
  160. package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
  161. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
  162. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
  163. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
  164. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
  165. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
  166. package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
  167. package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
  168. package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
  169. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
  170. package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
  171. package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
  172. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
  173. package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
  174. package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
  175. package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
  176. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
  177. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
  178. package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
  179. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
  180. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
  181. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
  182. package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
  183. package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
  184. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
  185. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
  186. package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
  187. package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
  188. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
  189. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
  190. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
  191. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
  192. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
  193. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
  194. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
  195. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
  196. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
  197. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
  198. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
  199. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
  200. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
  201. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
  202. package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
  203. package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
  204. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
  205. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
  206. package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
  207. package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
  208. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
  209. package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
  210. package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
  211. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
  212. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
  213. package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
  214. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
  215. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
  216. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
  217. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
  218. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
  219. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
  220. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
  221. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
  222. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
  223. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
  224. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
  225. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
  226. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
  227. package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
  228. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
  229. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
  230. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
  231. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
  232. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
  233. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
  234. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
  235. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
  236. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
  237. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
  238. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
  239. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
  240. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
  241. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
  242. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
  243. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
  244. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
  245. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
  246. package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
  247. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
  248. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
  249. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
  250. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
  251. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
  252. package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
  253. package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
  254. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
  255. package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
  256. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
  257. package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
  258. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
  259. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
  260. package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
  261. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
  262. package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
  263. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
  264. package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
@@ -1,500 +1,411 @@
1
1
  ---
2
2
  name: cloudkit-sync
3
- description: "Implement, review, or improve CloudKit and iCloud sync in iOS/macOS apps. Use when working with CKContainer, CKRecord, CKQuery, CKSubscription, CKSyncEngine, CKShare, NSUbiquitousKeyValueStore, or iCloud Drive file coordination; when syncing SwiftData models via ModelConfiguration with cloudKitDatabase; when handling CKError codes for conflict resolution, network failures, or quota limits; or when checking iCloud account status before performing sync operations."
3
+ description: "CloudKit and iCloud sync on iOS and macOS: CKContainer public, private and shared databases, CKRecord, CKQuery, CKSubscription, CKSyncEngine, CKShare, NSUbiquitousKeyValueStore, iCloud Drive file coordination, SwiftData sync via ModelConfiguration cloudKitDatabase, CKError handling for conflicts, network failures and quota, iCloud account status checks. Use when implementing, reviewing or improving how an app stores or shares user data through iCloud."
4
4
  metadata:
5
- source: "dpearson2699/swift-ios-skills (PolyForm Perimeter 1.0.0)"
5
+ source: multi-agent-pipeline
6
6
  ---
7
- # CloudKit
8
-
9
- Sync data across devices using CloudKit, iCloud key-value storage, and iCloud
10
- Drive. Covers container setup, record CRUD, queries, subscriptions, CKSyncEngine,
11
- SwiftData integration, conflict resolution, and error handling. Targets iOS 26+
12
- with Swift 6.3; older availability noted where relevant.
13
-
14
- ## Contents
15
-
16
- - [Container and Database Setup](#container-and-database-setup)
17
- - [CKRecord CRUD](#ckrecord-crud)
18
- - [CKQuery](#ckquery)
19
- - [CKSubscription](#cksubscription)
20
- - [CKSyncEngine (iOS 17+)](#cksyncengine-ios-17)
21
- - [SwiftData + CloudKit](#swiftdata--cloudkit)
22
- - [NSUbiquitousKeyValueStore](#nsubiquitouskeyvaluestore)
23
- - [iCloud Drive File Sync](#icloud-drive-file-sync)
24
- - [Account Status and Error Handling](#account-status-and-error-handling)
25
- - [Conflict Resolution](#conflict-resolution)
26
- - [Common Mistakes](#common-mistakes)
27
- - [Review Checklist](#review-checklist)
28
- - [References](#references)
29
-
30
- ## Container and Database Setup
31
-
32
- Enable iCloud + CloudKit in Signing & Capabilities. A container provides three databases:
33
-
34
- | Database | Scope | Requires iCloud | Storage Quota |
35
- |----------|-------|-----------------|---------------|
36
- | Public | All users | Read: No, Write: Yes | App quota |
37
- | Private | Current user | Yes | User quota |
38
- | Shared | Shared records | Yes | Owner quota |
7
+
8
+ # CloudKit Sync
9
+
10
+ Everything iCloud offers for app data: CloudKit databases, the key-value
11
+ store, and iCloud Drive documents. This file covers containers, record
12
+ operations, queries, subscriptions, `CKSyncEngine`, SwiftData integration,
13
+ error handling and conflict merges for Swift 6.3 on iOS 26, with older
14
+ availability noted where it matters.
15
+
16
+ Incremental fetch operations, change tokens, sharing, zones, assets, batching,
17
+ quality of service, encrypted fields and the Dashboard are in the
18
+ [CloudKit patterns reference](references/cloudkit-patterns.md). Core Data
19
+ stacks that sync through `NSPersistentCloudKitContainer` start in `core-data`;
20
+ background push handling in general is `push-notifications`.
21
+
22
+ ## Containers and databases
23
+
24
+ Enable **iCloud** with the **CloudKit** service under Signing & Capabilities.
25
+
26
+ | Database | Who sees it | Needs an iCloud account | Storage charged to |
27
+ |---|---|---|---|
28
+ | Public | Every user of the app | Only for writes; reads work signed out | The app |
29
+ | Private | The signed-in user | Yes | The user |
30
+ | Shared | Records others shared with the user | Yes | The owner |
39
31
 
40
32
  ```swift
41
33
  import CloudKit
42
34
 
43
- let container = CKContainer.default()
44
- // Or named: CKContainer(identifier: "iCloud.com.example.app")
45
-
46
- let publicDB = container.publicCloudDatabase
35
+ let container = CKContainer(identifier: "iCloud.app.gardenlog") // or CKContainer.default()
36
+ let publicDB = container.publicCloudDatabase
47
37
  let privateDB = container.privateCloudDatabase
48
- let sharedDB = container.sharedCloudDatabase
38
+ let sharedDB = container.sharedCloudDatabase
49
39
  ```
50
40
 
51
- ## CKRecord CRUD
41
+ ## Records
52
42
 
53
- Records are key-value pairs. Max 1 MB per record (excluding CKAsset data).
43
+ A `CKRecord` is a typed dictionary. One record can hold up to 1 MB, not
44
+ counting the files behind any `CKAsset` fields.
54
45
 
55
46
  ```swift
56
- // CREATE
57
- let record = CKRecord(recordType: "Note")
58
- record["title"] = "Meeting Notes" as CKRecordValue
59
- record["body"] = "Discussed Q3 roadmap" as CKRecordValue
60
- record["createdAt"] = Date() as CKRecordValue
61
- record["tags"] = ["work", "planning"] as CKRecordValue
62
- let saved = try await privateDB.save(record)
63
-
64
- // FETCH by ID
65
- let recordID = CKRecord.ID(recordName: "unique-id-123")
66
- let fetched = try await privateDB.record(for: recordID)
67
-
68
- // UPDATE -- fetch first, modify, then save
69
- fetched["title"] = "Updated Title" as CKRecordValue
70
- let updated = try await privateDB.save(fetched)
71
-
72
- // DELETE
73
- try await privateDB.deleteRecord(withID: recordID)
47
+ func plant(named name: String, in db: CKDatabase) async throws -> CKRecord {
48
+ let plant = CKRecord(recordType: "Plant")
49
+ plant["name"] = name as CKRecordValue
50
+ plant["plantedOn"] = Date.now as CKRecordValue
51
+ plant["tags"] = ["herb", "sunny"] as CKRecordValue
52
+ return try await db.save(plant)
53
+ }
54
+
55
+ func rename(_ recordName: String, to newName: String, in db: CKDatabase) async throws {
56
+ let id = CKRecord.ID(recordName: recordName)
57
+ let existing = try await db.record(for: id) // always fetch before editing
58
+ existing["name"] = newName as CKRecordValue
59
+ _ = try await db.save(existing)
60
+ }
61
+
62
+ func uproot(_ recordName: String, in db: CKDatabase) async throws {
63
+ _ = try await db.deleteRecord(withID: CKRecord.ID(recordName: recordName))
64
+ }
74
65
  ```
75
66
 
76
- ### Custom Record Zones
67
+ ### Custom zones
77
68
 
78
- Apps create custom zones in the private database. Shared databases expose zones
79
- that other users share with the current user. Custom zones support atomic
80
- commits, change tracking, and sharing; public databases do not support custom
69
+ The private database accepts zones the app creates; the shared database
70
+ exposes the zones other people shared with the user. Custom zones unlock
71
+ atomic commits, change tracking and sharing. The public database has no custom
81
72
  zones.
82
73
 
83
74
  ```swift
84
- let zoneID = CKRecordZone.ID(zoneName: "NotesZone")
85
- let zone = CKRecordZone(zoneID: zoneID)
86
- try await privateDB.save(zone)
75
+ let beds = CKRecordZone.ID(zoneName: "GardenBeds")
76
+ _ = try await privateDB.save(CKRecordZone(zoneID: beds))
87
77
 
88
- let recordID = CKRecord.ID(recordName: UUID().uuidString, zoneID: zoneID)
89
- let record = CKRecord(recordType: "Note", recordID: recordID)
78
+ let bedID = CKRecord.ID(recordName: UUID().uuidString, zoneID: beds)
79
+ let bed = CKRecord(recordType: "Bed", recordID: bedID)
90
80
  ```
91
81
 
92
- ## CKQuery
93
-
94
- Query records with NSPredicate. Supported: `==`, `!=`, `<`, `>`, `<=`, `>=`,
95
- `BEGINSWITH`, `CONTAINS`, `IN`, `AND`, `NOT`, `BETWEEN`,
96
- `distanceToLocation:fromLocation:`.
82
+ ## Queries
97
83
 
98
- `CONTAINS` tests list membership except for tokenized full-text search with
99
- `self CONTAINS`. `BEGINSWITH` is the string-prefix operator; unsupported
100
- operators, key paths, or field types fail when the query executes.
101
- For every encryption review, explicitly call out field eligibility: encrypted
102
- values cannot be queried or sorted; `CKAsset` is encrypted by default; and
103
- `CKRecord.Reference` cannot be encrypted because CloudKit needs it server-side.
84
+ CloudKit accepts a subset of `NSPredicate`:
104
85
 
105
- ```swift
106
- let predicate = NSPredicate(format: "title BEGINSWITH %@", "Meeting")
107
- let query = CKQuery(recordType: "Note", predicate: predicate)
108
- query.sortDescriptors = [NSSortDescriptor(key: "createdAt", ascending: false)]
109
-
110
- let (results, _) = try await privateDB.records(matching: query)
111
- for (_, result) in results {
112
- let record = try result.get()
113
- print(record["title"] as? String ?? "")
114
- }
86
+ - comparisons `==`, `!=`, `<`, `>`, `<=`, `>=`
87
+ - `BEGINSWITH` for string prefixes
88
+ - `CONTAINS` for membership in a list field, except `self CONTAINS`, which runs a tokenized full-text search
89
+ - `IN`, `AND`, `NOT`, `BETWEEN`
90
+ - `distanceToLocation:fromLocation:` for location fields
115
91
 
116
- // Fetch all records of a type
117
- let allQuery = CKQuery(recordType: "Note", predicate: NSPredicate(value: true))
92
+ An unsupported operator, key path or field type is only rejected when the
93
+ query executes, not when you build it.
118
94
 
119
- // Full-text search across string fields
120
- let searchQuery = CKQuery(
121
- recordType: "Note",
122
- predicate: NSPredicate(format: "self CONTAINS %@", "roadmap")
123
- )
95
+ Encrypted values cannot be queried or sorted. `CKAsset` contents are already
96
+ encrypted, and a `CKRecord.Reference` can never be encrypted because the
97
+ server needs it. Say all three whenever you review encryption.
124
98
 
125
- // Compound predicate
126
- let compound = NSCompoundPredicate(andPredicateWithSubpredicates: [
127
- NSPredicate(format: "createdAt > %@", cutoffDate as NSDate),
128
- NSPredicate(format: "tags CONTAINS %@", "work")
129
- ])
99
+ ```swift
100
+ func recentHerbs(in db: CKDatabase) async throws -> [CKRecord] {
101
+ let weekAgo = Date.now.addingTimeInterval(-7 * 86_400)
102
+ let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
103
+ NSPredicate(format: "plantedOn > %@", weekAgo as NSDate),
104
+ NSPredicate(format: "tags CONTAINS %@", "herb"),
105
+ ])
106
+ let query = CKQuery(recordType: "Plant", predicate: predicate)
107
+ query.sortDescriptors = [NSSortDescriptor(key: "plantedOn", ascending: false)]
108
+
109
+ let (matches, _) = try await db.records(matching: query)
110
+ return matches.compactMap { try? $0.1.get() }
111
+ }
130
112
  ```
131
113
 
132
- ## CKSubscription
114
+ Use `NSPredicate(value: true)` to match every record of a type.
133
115
 
134
- Subscriptions trigger push notifications when records change server-side.
135
- CloudKit/Xcode handles the APNs entitlement when CloudKit is enabled; no
136
- separate explicit App ID push setup is needed. Silent/background processing
137
- still needs Background Modes > Remote notifications.
116
+ ## Subscriptions
117
+
118
+ A subscription makes CloudKit push a notification when server data changes.
119
+ With CloudKit enabled, Xcode manages the push entitlement; no separate App ID
120
+ push setup is needed. Silent pushes still require **Background Modes >
121
+ Remote notifications**.
138
122
 
139
123
  ```swift
140
- // Query subscription -- fires when matching records change
141
- let subscription = CKQuerySubscription(
142
- recordType: "Note",
143
- predicate: NSPredicate(format: "tags CONTAINS %@", "urgent"),
144
- subscriptionID: "urgent-notes",
124
+ let newPlants = CKQuerySubscription(
125
+ recordType: "Plant",
126
+ predicate: NSPredicate(value: true),
127
+ subscriptionID: "plant-changes",
145
128
  options: [.firesOnRecordCreation, .firesOnRecordUpdate]
146
129
  )
147
- let notifInfo = CKSubscription.NotificationInfo()
148
- notifInfo.shouldSendContentAvailable = true // silent push
149
- subscription.notificationInfo = notifInfo
150
- try await privateDB.save(subscription)
151
-
152
- // Database subscription -- fires on any database change
153
- let dbSub = CKDatabaseSubscription(subscriptionID: "private-db-changes")
154
- dbSub.notificationInfo = notifInfo
155
- try await privateDB.save(dbSub)
156
-
157
- // Record zone subscription -- fires on changes within a zone
158
- let zoneSub = CKRecordZoneSubscription(
159
- zoneID: CKRecordZone.ID(zoneName: "NotesZone"),
160
- subscriptionID: "notes-zone-changes"
161
- )
162
- zoneSub.notificationInfo = notifInfo
163
- try await privateDB.save(zoneSub)
164
- ```
130
+ let info = CKSubscription.NotificationInfo()
131
+ info.shouldSendContentAvailable = true // silent
132
+ newPlants.notificationInfo = info
133
+ _ = try await publicDB.save(newPlants)
165
134
 
166
- Handle in AppDelegate:
135
+ let everything = CKDatabaseSubscription(subscriptionID: "private-db") // any change in the DB
136
+ let oneZone = CKRecordZoneSubscription(zoneID: beds, subscriptionID: "beds") // changes in one zone
137
+ ```
167
138
 
168
139
  ```swift
169
- func application(
170
- _ application: UIApplication,
171
- didReceiveRemoteNotification userInfo: [AnyHashable: Any]
172
- ) async -> UIBackgroundFetchResult {
173
- let notification = CKNotification(fromRemoteNotificationDictionary: userInfo)
174
- guard notification?.subscriptionID == "private-db-changes" else { return .noData }
175
- // Fetch changes using CKSyncEngine or CKFetchRecordZoneChangesOperation
176
- return .newData
140
+ import UIKit
141
+
142
+ // UIApplicationDelegate
143
+ func application(_ application: UIApplication,
144
+ didReceiveRemoteNotification userInfo: [AnyHashable: Any]) async -> UIBackgroundFetchResult {
145
+ guard let note = CKNotification(fromRemoteNotificationDictionary: userInfo),
146
+ note.subscriptionID == "private-db" else { return .noData }
147
+ do {
148
+ try await GardenSync.shared.pullNow() // CKSyncEngine or CKFetchRecordZoneChangesOperation
149
+ return .newData
150
+ } catch {
151
+ return .failed
152
+ }
177
153
  }
178
154
  ```
179
155
 
180
156
  ## CKSyncEngine (iOS 17+)
181
157
 
182
- `CKSyncEngine` is the recommended sync approach for custom model data. It
183
- handles scheduling, transient retries, change tokens, and database
184
- subscriptions, but not app-specific save failures: `CKError.serverRecordChanged`
185
- from `sentRecordZoneChanges.failedRecordSaves` still requires custom conflict
186
- resolution and rescheduling. Automatic sync timing is indeterminate. Requires
187
- CloudKit capability + Remote notifications; private/shared databases only.
158
+ For custom model data, `CKSyncEngine` is the recommended route. It schedules
159
+ work, retries transient failures, keeps change tokens and manages the
160
+ database subscription for you. It does **not** resolve app-level save
161
+ failures: a `CKError.serverRecordChanged` inside
162
+ `sentRecordZoneChanges.failedRecordSaves` needs your merge and a reschedule.
163
+ Its automatic timing is not predictable. It needs the CloudKit capability plus
164
+ Remote notifications, and works only with private and shared databases, never
165
+ public.
188
166
 
189
167
  ```swift
190
- import CloudKit
191
-
192
- final class SyncManager: CKSyncEngineDelegate {
193
- let syncEngine: CKSyncEngine
168
+ actor GardenSync: CKSyncEngineDelegate {
169
+ static let shared = GardenSync()
170
+ private var engine: CKSyncEngine?
194
171
 
195
- init(container: CKContainer = .default()) {
172
+ func start() {
173
+ guard engine == nil else { return }
196
174
  let config = CKSyncEngine.Configuration(
197
- database: container.privateCloudDatabase,
198
- stateSerialization: Self.loadState(),
175
+ database: CKContainer.default().privateCloudDatabase,
176
+ stateSerialization: StateFile.load(), // CKSyncEngine.State.Serialization?
199
177
  delegate: self
200
178
  )
201
- self.syncEngine = CKSyncEngine(config)
179
+ engine = CKSyncEngine(config)
202
180
  }
203
181
 
204
182
  func handleEvent(_ event: CKSyncEngine.Event, syncEngine: CKSyncEngine) async {
205
183
  switch event {
206
184
  case .stateUpdate(let update):
207
- Self.saveState(update.stateSerialization)
185
+ StateFile.save(update.stateSerialization) // resume from the right token next launch
208
186
  case .accountChange(let change):
209
- handleAccountChange(change)
210
- case .fetchedRecordZoneChanges(let changes):
211
- for mod in changes.modifications { processRemoteRecord(mod.record) }
212
- for del in changes.deletions { processRemoteDeletion(del.recordID) }
187
+ await LocalStore.shared.handle(change)
188
+ case .fetchedRecordZoneChanges(let fetched):
189
+ for mod in fetched.modifications { await LocalStore.shared.upsert(mod.record) }
190
+ for gone in fetched.deletions { await LocalStore.shared.remove(gone.recordID) }
213
191
  case .sentRecordZoneChanges(let sent):
214
- for saved in sent.savedRecords { markSynced(saved) }
215
- for fail in sent.failedRecordSaves { handleSaveFailure(fail) }
216
- default: break
192
+ for saved in sent.savedRecords { await LocalStore.shared.markSynced(saved) }
193
+ for failure in sent.failedRecordSaves { await resolve(failure, engine: syncEngine) }
194
+ default:
195
+ break
217
196
  }
218
197
  }
219
198
 
220
- func nextRecordZoneChangeBatch(
221
- _ context: CKSyncEngine.SendChangesContext,
222
- syncEngine: CKSyncEngine
223
- ) async -> CKSyncEngine.RecordZoneChangeBatch? {
224
- let pending = syncEngine.state.pendingRecordZoneChanges
225
- .filter { context.options.zoneIDs.contains($0) }
226
- return await CKSyncEngine.RecordZoneChangeBatch(
227
- pendingChanges: pending
228
- ) { recordID in self.recordToSend(for: recordID) }
199
+ func nextRecordZoneChangeBatch(_ context: CKSyncEngine.SendChangesContext,
200
+ syncEngine: CKSyncEngine) async -> CKSyncEngine.RecordZoneChangeBatch? {
201
+ let scope = context.options.scope
202
+ let pending = syncEngine.state.pendingRecordZoneChanges.filter { scope.contains($0) }
203
+ return await CKSyncEngine.RecordZoneChangeBatch(pendingChanges: pending) { id in
204
+ await LocalStore.shared.record(for: id)
205
+ }
229
206
  }
230
- }
231
207
 
232
- // Schedule changes
233
- let zoneID = CKRecordZone.ID(zoneName: "NotesZone")
234
- let recordID = CKRecord.ID(recordName: noteID, zoneID: zoneID)
235
- syncEngine.state.add(pendingRecordZoneChanges: [.saveRecord(recordID)])
208
+ func queueUpload(of id: CKRecord.ID) {
209
+ engine?.state.add(pendingRecordZoneChanges: [.saveRecord(id)])
210
+ }
236
211
 
237
- // Trigger immediate sync (pull-to-refresh)
238
- try await syncEngine.fetchChanges()
239
- try await syncEngine.sendChanges()
212
+ func pullNow() async throws {
213
+ guard let engine else { return }
214
+ try await engine.fetchChanges() // pull-to-refresh or push arrival
215
+ try await engine.sendChanges()
216
+ }
217
+ }
240
218
  ```
241
219
 
242
- **Key point**: persist `stateSerialization` across launches; the engine needs it
243
- to resume from the correct change token.
220
+ `context.options.scope` says which zones (for example `.zoneIDs([...])`) or
221
+ records this send covers; filter the pending changes to it rather than
222
+ sending everything. Both delegate methods are `async`. Persist
223
+ `stateSerialization` on every `.stateUpdate`; without it the engine starts
224
+ over on the next launch.
244
225
 
245
- ## SwiftData + CloudKit
226
+ ## SwiftData with CloudKit
246
227
 
247
- `ModelConfiguration` supports CloudKit sync. In every SwiftData CloudKit
248
- implementation or review, always report two verdicts:
228
+ `ModelConfiguration` can sync a SwiftData store to CloudKit. Any
229
+ implementation or review has to state **two verdicts**:
249
230
 
250
- - **Model compatibility**: no `#Unique` or unique constraints, optional
251
- relationships, no `.deny`, and external storage for large `Data`.
252
- - **Schema rollout**: initialize the development schema in nonproduction builds,
253
- verify it in CloudKit Dashboard, promote it before release, and after
254
- production promotion only add schema; don't delete model types or change
255
- existing attributes.
231
+ 1. **Model compatibility** - no `#Unique` macro or unique attributes, every
232
+ relationship optional, no `.deny` delete rule, and large `Data` marked for
233
+ external storage. Scalar properties do not all need to be optional; they
234
+ just need defaults or optionality where CloudKit requires it.
235
+ 2. **Schema rollout** - initialize the development schema from a non-release
236
+ build, inspect it in CloudKit Dashboard, and promote it to production before
237
+ shipping. After promotion, changes are additive only: never delete a model
238
+ type or change an existing attribute.
256
239
 
257
240
  ```swift
258
241
  import SwiftData
259
242
 
260
243
  @Model
261
- class Note {
262
- var title: String
263
- var body: String?
264
- var createdAt: Date?
265
- @Attribute(.externalStorage) var imageData: Data?
266
-
267
- init(title: String, body: String? = nil) {
268
- self.title = title
269
- self.body = body
270
- self.createdAt = Date()
271
- }
244
+ final class Harvest {
245
+ var crop: String = ""
246
+ var weightGrams: Double = 0
247
+ var pickedOn: Date = Date.now
248
+ @Attribute(.externalStorage) var photo: Data?
249
+ var bed: GardenBed? // optional relationship
250
+ init() {}
272
251
  }
252
+ ```
273
253
 
274
- let config = ModelConfiguration(
275
- "Notes",
276
- cloudKitDatabase: .private("iCloud.com.example.app")
277
- )
278
- let container = try ModelContainer(for: Note.self, configurations: config)
254
+ ```swift
255
+ let config = ModelConfiguration("Garden", cloudKitDatabase: .private("iCloud.app.gardenlog"))
256
+ let container = try ModelContainer(for: Harvest.self, GardenBed.self, configurations: config)
279
257
  ```
280
258
 
281
- ## NSUbiquitousKeyValueStore
259
+ ## Key-value store
282
260
 
283
- Simple key-value sync. Max 1024 keys, 1 MB total, 1 MB per value. Stores
284
- locally when iCloud is unavailable.
261
+ `NSUbiquitousKeyValueStore` holds small preferences: at most 1024 keys, 1 MB
262
+ in total and 1 MB per value. It writes locally when iCloud is unavailable and
263
+ syncs later.
285
264
 
286
265
  ```swift
287
- let kvStore = NSUbiquitousKeyValueStore.default
288
-
289
- // Write
290
- kvStore.set("dark", forKey: "theme")
291
- kvStore.set(14.0, forKey: "fontSize")
292
- kvStore.set(true, forKey: "notificationsEnabled")
293
- kvStore.synchronize()
266
+ let kv = NSUbiquitousKeyValueStore.default
267
+ kv.set("celsius", forKey: "temperatureUnit")
268
+ kv.synchronize()
269
+ let unit = kv.string(forKey: "temperatureUnit")
294
270
 
295
- // Read
296
- let theme = kvStore.string(forKey: "theme") ?? "system"
297
-
298
- // Observe external changes
299
271
  NotificationCenter.default.addObserver(
300
272
  forName: NSUbiquitousKeyValueStore.didChangeExternallyNotification,
301
- object: kvStore, queue: .main
302
- ) { notification in
303
- guard let userInfo = notification.userInfo,
304
- let reason = userInfo[NSUbiquitousKeyValueStoreChangeReasonKey] as? Int,
305
- let keys = userInfo[NSUbiquitousKeyValueStoreChangedKeysKey] as? [String]
306
- else { return }
307
-
273
+ object: kv, queue: .main
274
+ ) { note in
275
+ let reason = note.userInfo?[NSUbiquitousKeyValueStoreChangeReasonKey] as? Int
276
+ let keys = note.userInfo?[NSUbiquitousKeyValueStoreChangedKeysKey] as? [String] ?? []
308
277
  switch reason {
309
- case NSUbiquitousKeyValueStoreServerChange:
310
- for key in keys { applyRemoteChange(key: key) }
311
- case NSUbiquitousKeyValueStoreInitialSyncChange:
312
- reloadAllSettings()
313
- case NSUbiquitousKeyValueStoreQuotaViolationChange:
314
- handleQuotaExceeded()
278
+ case NSUbiquitousKeyValueStoreServerChange: Preferences.apply(keys)
279
+ case NSUbiquitousKeyValueStoreInitialSyncChange: Preferences.reloadAll()
280
+ case NSUbiquitousKeyValueStoreQuotaViolationChange: Preferences.trimStorage()
315
281
  default: break
316
282
  }
317
283
  }
318
284
  ```
319
285
 
320
- ## iCloud Drive File Sync
286
+ ## iCloud Drive documents
321
287
 
322
- Use `FileManager` ubiquity APIs for document-level sync. Call
323
- `url(forUbiquityContainerIdentifier:)` and `setUbiquitous` off the main thread;
324
- `setUbiquitous` performs coordinated file work and can block. If the app is
325
- presenting the file, configure an active file presenter before moving it.
288
+ Documents sync through the `FileManager` ubiquity APIs. Both
289
+ `url(forUbiquityContainerIdentifier:)` and
290
+ `setUbiquitous(_:itemAt:destinationURL:)` can block (the second performs
291
+ coordinated file operations), so call them off the main thread. A `nil`
292
+ container URL means iCloud is not available. If the app is currently
293
+ presenting the file, register a file presenter before moving it.
326
294
 
327
295
  ```swift
328
- Task.detached {
329
- guard let ubiquityURL = FileManager.default.url(
330
- forUbiquityContainerIdentifier: "iCloud.com.example.app"
331
- ) else { return } // iCloud not available
332
-
333
- let docsURL = ubiquityURL.appendingPathComponent("Documents")
334
- try FileManager.default.createDirectory(at: docsURL, withIntermediateDirectories: true)
335
- let cloudURL = docsURL.appendingPathComponent("report.pdf")
336
- try FileManager.default.setUbiquitous(true, itemAt: localURL, destinationURL: cloudURL)
296
+ func moveToCloud(_ local: URL) {
297
+ Task.detached {
298
+ let fm = FileManager.default
299
+ guard let root = fm.url(forUbiquityContainerIdentifier: nil) else { return }
300
+ let docs = root.appending(path: "Documents", directoryHint: .isDirectory)
301
+ try? fm.createDirectory(at: docs, withIntermediateDirectories: true)
302
+ try? fm.setUbiquitous(true, itemAt: local, destinationURL: docs.appending(path: local.lastPathComponent))
303
+ }
337
304
  }
338
305
  ```
339
306
 
340
- Monitor files with `NSMetadataQuery` scoped to
341
- `NSMetadataQueryUbiquitousDocumentsScope` or
342
- `NSMetadataQueryUbiquitousDataScope`.
307
+ Watch for changes with an `NSMetadataQuery` whose search scope is
308
+ `NSMetadataQueryUbiquitousDocumentsScope` or `NSMetadataQueryUbiquitousDataScope`.
343
309
 
344
- ## Account Status and Error Handling
310
+ ## Account status
345
311
 
346
- Always check account status before sync. Listen for `.CKAccountChanged`.
312
+ Check the account before any sync, and listen for `.CKAccountChanged`.
347
313
 
348
314
  ```swift
349
- func checkiCloudStatus() async throws -> CKAccountStatus {
350
- let status = try await CKContainer.default().accountStatus()
351
- switch status {
352
- case .available: return status
353
- case .noAccount: throw SyncError.noiCloudAccount
354
- case .restricted: throw SyncError.restricted
355
- case .temporarilyUnavailable: throw SyncError.temporarilyUnavailable
356
- case .couldNotDetermine: throw SyncError.unknown
357
- @unknown default: throw SyncError.unknown
358
- }
315
+ switch try await CKContainer.default().accountStatus() {
316
+ case .available: await GardenSync.shared.start()
317
+ case .noAccount: showSignInToICloudHint()
318
+ case .restricted: showRestrictedNotice()
319
+ case .temporarilyUnavailable: scheduleAccountRecheck()
320
+ case .couldNotDetermine: scheduleAccountRecheck()
321
+ @unknown default: scheduleAccountRecheck()
359
322
  }
360
323
  ```
361
324
 
362
- ### CKError Handling
325
+ ## CKError handling
363
326
 
364
- | Error Code | Strategy |
365
- |-----------|----------|
366
- | `.networkFailure`, `.networkUnavailable` | Queue for retry when network returns |
367
- | `.serverRecordChanged` | Three-way merge (see Conflict Resolution) |
368
- | `.requestRateLimited`, `.zoneBusy`, `.serviceUnavailable` | Retry after `retryAfterSeconds` |
369
- | `.quotaExceeded` | Notify user; reduce data usage |
370
- | `.notAuthenticated` | Prompt iCloud sign-in |
371
- | `.partialFailure` | Inspect `partialErrorsByItemID` per item |
372
- | `.changeTokenExpired` | Reset token, refetch all changes |
373
- | `.userDeletedZone` | Recreate zone and re-upload data |
327
+ | Code | Response |
328
+ |---|---|
329
+ | `.networkFailure`, `.networkUnavailable` | Queue the work and retry once connectivity returns |
330
+ | `.serverRecordChanged` | Three-way merge (below) |
331
+ | `.requestRateLimited`, `.zoneBusy`, `.serviceUnavailable` | Wait `retryAfterSeconds` before retrying |
332
+ | `.quotaExceeded` | Tell the user; reduce stored data |
333
+ | `.notAuthenticated` | Ask the user to sign in to iCloud |
334
+ | `.partialFailure` | Walk `partialErrorsByItemID` and handle each item's error |
335
+ | `.changeTokenExpired` | Drop the token and refetch everything |
336
+ | `.userDeletedZone` | Recreate the zone and upload again |
374
337
 
375
338
  ```swift
376
- func handleCloudKitError(_ error: Error) {
377
- guard let ckError = error as? CKError else { return }
378
- switch ckError.code {
379
- case .networkFailure, .networkUnavailable:
380
- scheduleRetryWhenOnline()
381
- case .serverRecordChanged:
382
- resolveConflict(ckError)
383
- case .requestRateLimited, .zoneBusy, .serviceUnavailable:
384
- let delay = ckError.retryAfterSeconds ?? 3.0
385
- scheduleRetry(after: delay)
386
- case .quotaExceeded:
387
- notifyUserStorageFull()
388
- case .partialFailure:
389
- if let partial = ckError.partialErrorsByItemID {
390
- for (_, itemError) in partial { handleCloudKitError(itemError) }
391
- }
392
- case .changeTokenExpired:
393
- resetChangeToken()
394
- case .userDeletedZone:
395
- recreateZoneAndResync()
396
- default: logError(ckError)
397
- }
339
+ func retryDelay(for error: CKError) -> TimeInterval {
340
+ error.retryAfterSeconds ?? 3
398
341
  }
399
342
  ```
400
343
 
401
- ## Conflict Resolution
344
+ ## Conflict resolution
402
345
 
403
- When saving a record that changed server-side, CloudKit returns
404
- `.serverRecordChanged` with three record versions. Always merge into
405
- `serverRecord` -- it has the correct change tag.
346
+ A `.serverRecordChanged` error carries `ancestorRecord`, `clientRecord` and
347
+ `serverRecord`. Always merge **into `serverRecord`**, because it holds the
348
+ current change tag; saving the client copy just fails again.
406
349
 
407
350
  ```swift
408
- func resolveConflict(_ error: CKError) {
409
- guard error.code == .serverRecordChanged,
410
- let ancestor = error.ancestorRecord,
411
- let client = error.clientRecord,
412
- let server = error.serverRecord
413
- else { return }
414
-
415
- // Merge client changes into server record
416
- for key in client.changedKeys() {
417
- if server[key] == ancestor[key] {
418
- server[key] = client[key] // Server unchanged, use client
419
- } else if client[key] == ancestor[key] {
420
- // Client unchanged, keep server (already there)
351
+ func merged(_ error: CKError) -> CKRecord? {
352
+ guard let server = error.serverRecord, let mine = error.clientRecord else { return nil }
353
+ let base = error.ancestorRecord
354
+ for key in mine.changedKeys() {
355
+ let serverValue = server[key] as? NSObject
356
+ let baseValue = base?[key] as? NSObject
357
+ let myValue = mine[key] as? NSObject
358
+ if serverValue == baseValue {
359
+ server[key] = mine[key] // only I changed it
360
+ } else if myValue == baseValue {
361
+ continue // only the server changed it
421
362
  } else {
422
- server[key] = mergeValues( // Both changed, custom merge
423
- ancestor: ancestor[key], client: client[key], server: server[key])
363
+ server[key] = resolveBothChanged(key, mine: mine[key], theirs: server[key])
424
364
  }
425
365
  }
426
-
427
- Task { try await CKContainer.default().privateCloudDatabase.save(server) }
366
+ return server // save this one
428
367
  }
429
368
  ```
430
369
 
431
- ## Common Mistakes
432
-
433
- **DON'T:** Perform sync operations without checking account status.
434
- **DO:** Check `CKContainer.accountStatus()` first; handle `.noAccount`.
435
- ```swift
436
- // WRONG
437
- try await privateDB.save(record)
438
- // CORRECT
439
- guard try await CKContainer.default().accountStatus() == .available
440
- else { throw SyncError.noiCloudAccount }
441
- try await privateDB.save(record)
442
- ```
443
-
444
- **DON'T:** Ignore `.serverRecordChanged` errors.
445
- **DO:** Implement three-way merge using ancestor, client, and server records.
446
-
447
- **DON'T:** Store user-specific data in the public database.
448
- **DO:** Use private database for personal data; public only for app-wide content.
449
-
450
- **DON'T:** Poll for changes on a timer.
451
- **DO:** Use `CKDatabaseSubscription` or `CKSyncEngine` for push-based sync.
452
- ```swift
453
- // WRONG
454
- Timer.scheduledTimer(withTimeInterval: 30, repeats: true) { _ in fetchAll() }
455
- // CORRECT
456
- let sub = CKDatabaseSubscription(subscriptionID: "db-changes")
457
- sub.notificationInfo = CKSubscription.NotificationInfo()
458
- sub.notificationInfo?.shouldSendContentAvailable = true
459
- try await privateDB.save(sub)
460
- ```
461
-
462
- **DON'T:** Retry immediately on rate limiting.
463
- **DO:** Use `CKError.retryAfterSeconds` to wait the required duration.
464
-
465
- **DON'T:** Assume `CKSyncEngine` handles `.serverRecordChanged` conflicts for you.
466
- **DO:** Resolve `failedRecordSaves` with a three-way merge, then reschedule the save.
467
-
468
- **DON'T:** Pass nil change token on every fetch.
469
- **DO:** Persist change tokens to disk and supply them on subsequent fetches.
470
-
471
- ## Review Checklist
472
-
473
- - [ ] iCloud + CloudKit capability enabled in Signing & Capabilities
474
- - [ ] Account status checked before sync; `.noAccount` handled gracefully
475
- - [ ] Private database used for user data; public only for shared content
476
- - [ ] Custom record zones created in private DB; shared DB zones discovered from shares
477
- - [ ] `CKError.serverRecordChanged` handled with three-way merge into `serverRecord`
478
- - [ ] Network failures queued for retry; `retryAfterSeconds` respected
479
- - [ ] `CKDatabaseSubscription` or `CKSyncEngine` used for push-based sync; Remote notifications enabled for background delivery
480
- - [ ] Change tokens persisted to disk; `changeTokenExpired` resets and refetches
481
- - [ ] `.partialFailure` errors inspected per-item via `partialErrorsByItemID`
482
- - [ ] `.userDeletedZone` handled by recreating zone and resyncing
483
- - [ ] SwiftData CloudKit review reports model compatibility and schema rollout: initialized/verified development schema, promoted before release, and additive-only production changes
484
- - [ ] `NSUbiquitousKeyValueStore.didChangeExternallyNotification` observed
485
- - [ ] Encryption review says `CKRecord.Reference` cannot use `encryptedValues` because CloudKit needs it server-side; no query/sort on encrypted fields; `CKAsset` is encrypted by default
486
- - [ ] `CKSyncEngine` state serialization persisted across launches (iOS 17+)
370
+ ## Common mistakes
371
+
372
+ - Starting sync without confirming `accountStatus() == .available`.
373
+ - Ignoring `.serverRecordChanged`, which silently loses edits.
374
+ - Putting personal data in the public database. Private is for user data;
375
+ public is only for content every user should see.
376
+ - Polling on a timer. Use a `CKDatabaseSubscription` with silent pushes or `CKSyncEngine`.
377
+ - Retrying a rate-limited request immediately instead of waiting `retryAfterSeconds`.
378
+ - Expecting `CKSyncEngine` to settle `.serverRecordChanged`. Merge the
379
+ `failedRecordSaves` yourself and reschedule them.
380
+ - Fetching with a `nil` change token every time. Store tokens on disk.
381
+
382
+ ## Review checklist
383
+
384
+ - [ ] iCloud capability with CloudKit enabled
385
+ - [ ] Account status checked; `.noAccount` handled
386
+ - [ ] User data kept in the private database
387
+ - [ ] Custom zones in the private database; shared zones discovered from accepted shares
388
+ - [ ] `.serverRecordChanged` merged into `serverRecord`
389
+ - [ ] Network failures queued; `retryAfterSeconds` honored
390
+ - [ ] Sync driven by pushes; Remote notifications background mode on
391
+ - [ ] Change tokens persisted; `.changeTokenExpired` resets them
392
+ - [ ] `.partialFailure` examined item by item
393
+ - [ ] `.userDeletedZone` handled
394
+ - [ ] SwiftData review gives both the model-compatibility and schema-rollout verdicts
395
+ - [ ] Key-value store external-change notification observed
396
+ - [ ] Encryption review mentions: references cannot be encrypted, encrypted fields cannot be queried or sorted, assets are encrypted already
397
+ - [ ] `CKSyncEngine` state serialization saved (iOS 17+)
487
398
 
488
399
  ## References
489
400
 
490
- - See [references/cloudkit-patterns.md](references/cloudkit-patterns.md) for incremental sync, CKShare, zones, CKAsset storage, batch operations, and Dashboard usage.
491
- - [CloudKit Framework](https://sosumi.ai/documentation/cloudkit)
492
- - [CKContainer](https://sosumi.ai/documentation/cloudkit/ckcontainer)
493
- - [CKRecord](https://sosumi.ai/documentation/cloudkit/ckrecord)
494
- - [CKQuery](https://sosumi.ai/documentation/cloudkit/ckquery)
495
- - [CKSubscription](https://sosumi.ai/documentation/cloudkit/cksubscription)
496
- - [CKSyncEngine](https://sosumi.ai/documentation/cloudkit/cksyncengine)
497
- - [CKShare](https://sosumi.ai/documentation/cloudkit/ckshare)
498
- - [CKError](https://sosumi.ai/documentation/cloudkit/ckerror)
499
- - [NSUbiquitousKeyValueStore](https://sosumi.ai/documentation/foundation/nsubiquitouskeyvaluestore)
500
- - [SwiftData CloudKit sync](https://sosumi.ai/documentation/swiftdata/syncing-model-data-across-a-persons-devices)
401
+ - [CloudKit patterns](references/cloudkit-patterns.md) - incremental fetches, tokens, sharing, zones, assets, batches, QoS, encryption, Dashboard
402
+ - [CloudKit](https://developer.apple.com/documentation/cloudkit)
403
+ - [CKContainer](https://developer.apple.com/documentation/cloudkit/ckcontainer)
404
+ - [CKRecord](https://developer.apple.com/documentation/cloudkit/ckrecord)
405
+ - [CKQuery](https://developer.apple.com/documentation/cloudkit/ckquery)
406
+ - [CKSubscription](https://developer.apple.com/documentation/cloudkit/cksubscription)
407
+ - [CKSyncEngine](https://developer.apple.com/documentation/cloudkit/cksyncengine-5sie5)
408
+ - [CKShare](https://developer.apple.com/documentation/cloudkit/ckshare)
409
+ - [CKError](https://developer.apple.com/documentation/cloudkit/ckerror)
410
+ - [NSUbiquitousKeyValueStore](https://developer.apple.com/documentation/foundation/nsubiquitouskeyvaluestore)
411
+ - SwiftData: the article on syncing model data across a person's devices