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