@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,496 +1,492 @@
1
1
  ---
2
2
  name: swift-codable
3
- description: "Implement Swift Codable models for JSON and property-list encoding and decoding with JSONDecoder, JSONEncoder, CodingKeys, and custom init(from:) or encode(to:). Use when parsing API responses, remapping keys, flattening nested JSON, handling date or data decoding strategies, decoding heterogeneous arrays, or integrating Codable with URLSession, SwiftData, or UserDefaults."
3
+ description: "Codable JSON and property-list models: JSONDecoder, JSONEncoder, CodingKeys, custom init(from:)/encode(to:), nested containers, date, data and key strategies, heterogeneous and lossy arrays, missing-key defaults. Use when parsing API responses, remapping keys, flattening nested JSON, choosing decoding strategies, or wiring Codable into URLSession, SwiftData or UserDefaults."
4
4
  metadata:
5
- source: "dpearson2699/swift-ios-skills (PolyForm Perimeter 1.0.0)"
5
+ source: multi-agent-pipeline
6
6
  ---
7
7
 
8
8
  # Swift Codable
9
9
 
10
- Encode and decode Swift types using `Codable` (`Encodable & Decodable`) with
11
- `JSONEncoder`, `JSONDecoder`, and related APIs. Targets Swift 6.3 / iOS 26+.
12
-
13
- ## Contents
14
-
15
- - [Basic Conformance](#basic-conformance)
16
- - [Custom CodingKeys](#custom-codingkeys)
17
- - [Custom Decoding and Encoding](#custom-decoding-and-encoding)
18
- - [Nested and Flattened Containers](#nested-and-flattened-containers)
19
- - [Heterogeneous Arrays](#heterogeneous-arrays)
20
- - [Date Decoding Strategies](#date-decoding-strategies)
21
- - [Data and Key Strategies](#data-and-key-strategies)
22
- - [Lossy Array Decoding](#lossy-array-decoding)
23
- - [Single Value Containers](#single-value-containers)
24
- - [Default Values for Missing Keys](#default-values-for-missing-keys)
25
- - [Encoder and Decoder Configuration](#encoder-and-decoder-configuration)
26
- - [Codable with URLSession](#codable-with-urlsession)
27
- - [Codable with SwiftData](#codable-with-swiftdata)
28
- - [Codable with UserDefaults](#codable-with-userdefaults)
29
- - [Common Mistakes](#common-mistakes)
30
- - [Review Checklist](#review-checklist)
31
- - [References](#references)
32
-
33
- ## Basic Conformance
34
-
35
- When all stored properties are themselves `Codable`, the compiler synthesizes
36
- conformance automatically:
10
+ How to turn JSON and property lists into Swift values and back, for Swift 6.3
11
+ and iOS 26. The compiler writes most of the code; this skill is about the
12
+ places where it cannot, and about the boundaries where Codable meets
13
+ networking, SwiftData and `@AppStorage`.
14
+
15
+ ## Basic conformance
16
+
17
+ When every stored property is itself Codable, the compiler synthesizes the
18
+ whole conformance.
37
19
 
38
20
  ```swift
39
- struct User: Codable {
21
+ struct Recipe: Codable {
40
22
  let id: Int
41
- let name: String
42
- let email: String
43
- let isVerified: Bool
23
+ let title: String
24
+ let servings: Int
44
25
  }
45
26
 
46
- let user = try JSONDecoder().decode(User.self, from: jsonData)
47
- let encoded = try JSONEncoder().encode(user)
27
+ let recipe = try JSONDecoder().decode(Recipe.self, from: payload)
28
+ let bytes = try JSONEncoder().encode(recipe)
48
29
  ```
49
30
 
50
- Prefer `Decodable` for read-only API responses and `Encodable` for write-only.
51
- Use `Codable` only when both directions are required.
31
+ Pick the narrowest protocol:
32
+
33
+ - `Decodable` for data you only read, such as most API responses.
34
+ - `Encodable` for data you only send.
35
+ - `Codable` only when the type really travels both ways.
52
36
 
53
37
  ## Custom CodingKeys
54
38
 
55
- Rename JSON keys without writing a custom decoder by declaring a `CodingKeys`
56
- enum:
39
+ A `CodingKeys` enum renames keys and still leaves decoding to the compiler.
57
40
 
58
41
  ```swift
59
- struct Product: Codable {
42
+ struct Author: Decodable {
60
43
  let id: Int
61
- let displayName: String
62
- let imageURL: URL
63
- let priceInCents: Int
44
+ let penName: String
45
+ let avatarURL: URL
64
46
 
65
47
  enum CodingKeys: String, CodingKey {
66
48
  case id
67
- case displayName = "display_name"
68
- case imageURL = "image_url"
69
- case priceInCents = "price_in_cents"
49
+ case penName = "pen_name"
50
+ case avatarURL = "avatar_url"
70
51
  }
71
52
  }
72
53
  ```
73
54
 
74
- Every stored property must appear in the enum. Omitting a property from
75
- `CodingKeys` excludes it from encoding/decoding -- provide a default value or
76
- compute it separately.
55
+ Once the enum exists, it is the complete list of coded properties. A property
56
+ left out is not decoded or encoded at all, so it must have a default value or
57
+ be computed some other way.
77
58
 
78
- ## Custom Decoding and Encoding
59
+ ## Custom decoding and encoding
79
60
 
80
- Override `init(from:)` and `encode(to:)` for transformations the synthesized
81
- conformance cannot handle:
61
+ Write `init(from:)` and `encode(to:)` when a value needs converting on the
62
+ way in or out.
82
63
 
83
64
  ```swift
84
- struct Event: Codable {
85
- let name: String
86
- let timestamp: Date
87
- let tags: [String]
65
+ struct Shipment: Codable {
66
+ let reference: String
67
+ let dispatchedAt: Date
68
+ let labels: [String]
88
69
 
89
70
  enum CodingKeys: String, CodingKey {
90
- case name, timestamp, tags
71
+ case reference, dispatchedAt = "dispatched_epoch", labels
91
72
  }
92
73
 
93
- init(from decoder: Decoder) throws {
94
- let container = try decoder.container(keyedBy: CodingKeys.self)
95
- name = try container.decode(String.self, forKey: .name)
96
- // Decode Unix timestamp as Double, convert to Date
97
- let epoch = try container.decode(Double.self, forKey: .timestamp)
98
- timestamp = Date(timeIntervalSince1970: epoch)
99
- // Default to empty array when key is missing
100
- tags = try container.decodeIfPresent([String].self, forKey: .tags) ?? []
74
+ init(from decoder: any Decoder) throws {
75
+ let fields = try decoder.container(keyedBy: CodingKeys.self)
76
+ reference = try fields.decode(String.self, forKey: .reference)
77
+ let seconds = try fields.decode(Double.self, forKey: .dispatchedAt)
78
+ dispatchedAt = Date(timeIntervalSince1970: seconds)
79
+ labels = try fields.decodeIfPresent([String].self, forKey: .labels) ?? []
101
80
  }
102
81
 
103
- func encode(to encoder: Encoder) throws {
104
- var container = encoder.container(keyedBy: CodingKeys.self)
105
- try container.encode(name, forKey: .name)
106
- try container.encode(timestamp.timeIntervalSince1970, forKey: .timestamp)
107
- try container.encode(tags, forKey: .tags)
82
+ func encode(to encoder: any Encoder) throws {
83
+ var fields = encoder.container(keyedBy: CodingKeys.self)
84
+ try fields.encode(reference, forKey: .reference)
85
+ try fields.encode(dispatchedAt.timeIntervalSince1970, forKey: .dispatchedAt)
86
+ try fields.encode(labels, forKey: .labels)
108
87
  }
109
88
  }
110
89
  ```
111
90
 
112
- ## Nested and Flattened Containers
91
+ Doing the conversion here, instead of patching values after decoding, keeps
92
+ invalid instances from ever existing.
113
93
 
114
- Use `nestedContainer(keyedBy:forKey:)` to navigate and flatten nested JSON:
94
+ ## Nested and flattened containers
95
+
96
+ JSON often nests what the app wants flat. `nestedContainer(keyedBy:forKey:)`
97
+ reaches into the inner object with its own key enum.
115
98
 
116
99
  ```swift
117
- // JSON: { "id": 1, "location": { "lat": 37.7749, "lng": -122.4194 } }
118
- struct Place: Decodable {
119
- let id: Int
100
+ // { "name": "Harbour Cafe", "geo": { "latitude": 41.0, "longitude": 29.0 } }
101
+ struct Venue: Decodable {
102
+ let name: String
120
103
  let latitude: Double
121
104
  let longitude: Double
122
105
 
123
- enum CodingKeys: String, CodingKey { case id, location }
124
- enum LocationKeys: String, CodingKey { case lat, lng }
106
+ enum OuterKeys: String, CodingKey { case name, geo }
107
+ enum GeoKeys: String, CodingKey { case latitude, longitude }
125
108
 
126
- init(from decoder: Decoder) throws {
127
- let container = try decoder.container(keyedBy: CodingKeys.self)
128
- id = try container.decode(Int.self, forKey: .id)
129
- let location = try container.nestedContainer(
130
- keyedBy: LocationKeys.self, forKey: .location)
131
- latitude = try location.decode(Double.self, forKey: .lat)
132
- longitude = try location.decode(Double.self, forKey: .lng)
109
+ init(from decoder: any Decoder) throws {
110
+ let outer = try decoder.container(keyedBy: OuterKeys.self)
111
+ name = try outer.decode(String.self, forKey: .name)
112
+ let geo = try outer.nestedContainer(keyedBy: GeoKeys.self, forKey: .geo)
113
+ latitude = try geo.decode(Double.self, forKey: .latitude)
114
+ longitude = try geo.decode(Double.self, forKey: .longitude)
133
115
  }
134
116
  }
135
117
  ```
136
118
 
137
- Chain multiple `nestedContainer` calls to flatten deeply nested structures.
138
- Also use `nestedUnkeyedContainer(forKey:)` for nested arrays.
119
+ Deeper structures chain further `nestedContainer` calls. An array inside a
120
+ keyed object comes from `nestedUnkeyedContainer(forKey:)`.
139
121
 
140
- ## Heterogeneous Arrays
122
+ ## Heterogeneous arrays
141
123
 
142
- Decode arrays of mixed types using a discriminator field:
124
+ For a list whose elements differ by a `kind` field, model the element as an
125
+ enum with associated values. Read the discriminator first, then the payload it
126
+ selects.
143
127
 
144
128
  ```swift
145
- // JSON: [{"type":"text","content":"Hello"},{"type":"image","url":"pic.jpg"}]
146
- enum ContentBlock: Decodable {
147
- case text(String)
148
- case image(URL)
149
-
150
- enum CodingKeys: String, CodingKey { case type, content, url }
151
-
152
- init(from decoder: Decoder) throws {
153
- let container = try decoder.container(keyedBy: CodingKeys.self)
154
- let type = try container.decode(String.self, forKey: .type)
155
- switch type {
156
- case "text":
157
- let content = try container.decode(String.self, forKey: .content)
158
- self = .text(content)
159
- case "image":
160
- let url = try container.decode(URL.self, forKey: .url)
161
- self = .image(url)
162
- default:
129
+ enum FeedItem: Decodable {
130
+ case note(String)
131
+ case photo(URL)
132
+ case poll(options: [String])
133
+
134
+ enum Keys: String, CodingKey { case kind, text, url, options }
135
+
136
+ init(from decoder: any Decoder) throws {
137
+ let item = try decoder.container(keyedBy: Keys.self)
138
+ switch try item.decode(String.self, forKey: .kind) {
139
+ case "note": self = .note(try item.decode(String.self, forKey: .text))
140
+ case "photo": self = .photo(try item.decode(URL.self, forKey: .url))
141
+ case "poll": self = .poll(options: try item.decode([String].self, forKey: .options))
142
+ case let other:
163
143
  throw DecodingError.dataCorruptedError(
164
- forKey: .type, in: container,
165
- debugDescription: "Unknown type: \(type)")
144
+ forKey: .kind, in: item, debugDescription: "No FeedItem kind named \(other)")
166
145
  }
167
146
  }
168
147
  }
169
148
 
170
- let blocks = try JSONDecoder().decode([ContentBlock].self, from: jsonData)
149
+ let feed = try JSONDecoder().decode([FeedItem].self, from: payload)
171
150
  ```
172
151
 
173
- ## Date Decoding Strategies
152
+ ## Date decoding strategies
174
153
 
175
- Configure `JSONDecoder.dateDecodingStrategy` to match your API:
154
+ Set the strategy on the decoder to match what the server sends.
176
155
 
177
156
  ```swift
178
- let decoder = JSONDecoder()
179
-
180
- // ISO 8601 (e.g., "2024-03-15T10:30:00Z")
181
- decoder.dateDecodingStrategy = .iso8601
182
-
183
- // Unix timestamp in seconds (e.g., 1710499800)
184
- decoder.dateDecodingStrategy = .secondsSince1970
185
-
186
- // Custom DateFormatter
187
- let formatter = DateFormatter()
188
- formatter.dateFormat = "yyyy-MM-dd"
189
- formatter.locale = Locale(identifier: "en_US_POSIX")
190
- formatter.timeZone = TimeZone(secondsFromGMT: 0)
191
- decoder.dateDecodingStrategy = .formatted(formatter)
192
-
193
- // Custom closure for multiple formats
194
- decoder.dateDecodingStrategy = .custom { decoder in
195
- let container = try decoder.singleValueContainer()
196
- let string = try container.decode(String.self)
197
- if let date = ISO8601DateFormatter().date(from: string) { return date }
198
- throw DecodingError.dataCorruptedError(
199
- in: container, debugDescription: "Cannot decode date: \(string)")
157
+ let reader = JSONDecoder()
158
+
159
+ reader.dateDecodingStrategy = .iso8601 // "2025-11-02T08:15:00Z"
160
+ reader.dateDecodingStrategy = .secondsSince1970 // 1762071300
161
+
162
+ let dayFormat = DateFormatter()
163
+ dayFormat.dateFormat = "dd.MM.yyyy"
164
+ dayFormat.locale = Locale(identifier: "en_US_POSIX")
165
+ dayFormat.timeZone = TimeZone(secondsFromGMT: 0)
166
+ reader.dateDecodingStrategy = .formatted(dayFormat) // "02.11.2025"
167
+ ```
168
+
169
+ When one field arrives in more than one shape, decode it by hand:
170
+
171
+ ```swift
172
+ reader.dateDecodingStrategy = .custom { decoder in
173
+ let raw = try decoder.singleValueContainer()
174
+ let text = try raw.decode(String.self)
175
+ let withFraction = ISO8601DateFormatter()
176
+ withFraction.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
177
+ if let date = withFraction.date(from: text) ?? ISO8601DateFormatter().date(from: text) {
178
+ return date
179
+ }
180
+ throw DecodingError.dataCorruptedError(in: raw, debugDescription: "Unreadable date: \(text)")
200
181
  }
201
182
  ```
202
183
 
203
- Set the matching strategy on `JSONEncoder`:
204
- `encoder.dateEncodingStrategy = .iso8601`
184
+ Give the encoder the matching strategy, for example
185
+ `writer.dateEncodingStrategy = .iso8601`, so values round-trip.
205
186
 
206
- ## Data and Key Strategies
187
+ ## Data and key strategies
207
188
 
208
189
  ```swift
209
- let decoder = JSONDecoder()
210
- decoder.dataDecodingStrategy = .base64 // Base64-encoded Data fields
211
- decoder.keyDecodingStrategy = .convertFromSnakeCase // simple keys only; not URL/ID spelling
212
- // {"user_name": "Alice"} maps to `var userName: String` -- no CodingKeys needed
213
-
214
- let encoder = JSONEncoder()
215
- encoder.dataEncodingStrategy = .base64
216
- encoder.keyEncodingStrategy = .convertToSnakeCase
190
+ reader.dataDecodingStrategy = .base64
191
+ writer.dataEncodingStrategy = .base64
192
+
193
+ reader.keyDecodingStrategy = .convertFromSnakeCase // "first_name" -> firstName
194
+ writer.keyEncodingStrategy = .convertToSnakeCase
217
195
  ```
218
196
 
219
- Use key strategies only for mechanical snake_case-to-camelCase mappings.
220
- `convertFromSnakeCase` maps by spelling, not Swift acronym/initialism policy:
221
- `image_url`, `base_uri`, and `user_id` match `imageUrl`, `baseUri`, and
222
- `userId` only. If the Swift model uses `imageURL`, `baseURI`, or `userID`,
223
- declare explicit `CodingKeys`; the strategy will not synthesize those names.
197
+ The snake-case strategies are mechanical. They split on underscores and
198
+ capitalize each following word, nothing more, so:
199
+
200
+ | JSON key | Property name produced |
201
+ | --- | --- |
202
+ | `avatar_url` | `avatarUrl` |
203
+ | `base_uri` | `baseUri` |
204
+ | `owner_id` | `ownerId` |
224
205
 
225
- ## Lossy Array Decoding
206
+ A model that spells those `avatarURL`, `baseURI` or `ownerID` needs explicit
207
+ `CodingKeys` for exactly those properties. The strategy still handles every
208
+ ordinary key.
226
209
 
227
- By default, one invalid element fails the entire array. Use a wrapper to skip
228
- invalid elements:
210
+ ## Lossy array decoding
211
+
212
+ By default a single bad element throws, and the whole array is lost. When a
213
+ feed is known to be unreliable, wrap each element so a failure becomes `nil`
214
+ and the container still moves on:
229
215
 
230
216
  ```swift
231
- struct LossyArray<Element: Decodable>: Decodable {
232
- let elements: [Element]
233
-
234
- init(from decoder: Decoder) throws {
235
- var container = try decoder.unkeyedContainer()
236
- var elements: [Element] = []
237
- while !container.isAtEnd {
238
- if let element = try? container.decode(Element.self) {
239
- elements.append(element)
240
- } else {
241
- _ = try? container.decode(AnyCodableValue.self) // advance past bad element
217
+ struct TolerantList<Element: Decodable>: Decodable {
218
+ let items: [Element]
219
+
220
+ private struct Slot: Decodable {
221
+ let value: Element?
222
+ init(from decoder: any Decoder) throws {
223
+ value = try? Element(from: decoder)
224
+ }
225
+ }
226
+
227
+ init(from decoder: any Decoder) throws {
228
+ var list = try decoder.unkeyedContainer()
229
+ var kept: [Element] = []
230
+ while !list.isAtEnd {
231
+ if let value = try list.decode(Slot.self).value {
232
+ kept.append(value)
242
233
  }
243
234
  }
244
- self.elements = elements
235
+ items = kept
245
236
  }
246
237
  }
247
- private struct AnyCodableValue: Decodable {}
248
238
  ```
249
239
 
250
- ## Single Value Containers
240
+ Decoding `Slot` always succeeds, so the unkeyed container always advances.
241
+ A skip trick that decodes an empty placeholder struct only works when the bad
242
+ element is a JSON object; a bad string or number would throw again and leave
243
+ the loop stuck on the same index.
251
244
 
252
- Wrap primitives for type safety using `singleValueContainer()`:
245
+ ## Single value containers
253
246
 
254
- ```swift
255
- struct UserID: Codable, Hashable {
256
- let rawValue: String
247
+ A wrapper type that should appear in JSON as a bare value uses the single value
248
+ container in both directions.
257
249
 
258
- init(_ rawValue: String) { self.rawValue = rawValue }
250
+ ```swift
251
+ struct OrderNumber: Codable, Hashable {
252
+ let text: String
259
253
 
260
- init(from decoder: Decoder) throws {
261
- let container = try decoder.singleValueContainer()
262
- rawValue = try container.decode(String.self)
254
+ init(from decoder: any Decoder) throws {
255
+ text = try decoder.singleValueContainer().decode(String.self)
263
256
  }
264
257
 
265
- func encode(to encoder: Encoder) throws {
266
- var container = encoder.singleValueContainer()
267
- try container.encode(rawValue)
258
+ func encode(to encoder: any Encoder) throws {
259
+ var slot = encoder.singleValueContainer()
260
+ try slot.encode(text)
268
261
  }
269
262
  }
270
- // JSON: "usr_abc123" decodes directly to UserID
263
+ // Encodes as "A-1042", not {"text":"A-1042"}.
271
264
  ```
272
265
 
273
- ## Default Values for Missing Keys
266
+ ## Default values for missing keys
274
267
 
275
- Stored property defaults such as `var theme = "system"` do not make synthesized
276
- `Decodable` tolerate a missing nonoptional key; synthesis still fails unless the
277
- property is optional or decoded manually. Use `decodeIfPresent` with
278
- nil-coalescing when a missing or null key should fall back:
268
+ A default on a stored property does not help synthesized decoding. With
269
+ `var language = "en"`, a payload without `language` still throws
270
+ `keyNotFound`. Either make the property optional or decode it yourself:
279
271
 
280
272
  ```swift
281
- struct Settings: Decodable {
282
- let theme: String
283
- let fontSize: Int
284
- let notificationsEnabled: Bool
285
-
286
- enum CodingKeys: String, CodingKey {
287
- case theme, fontSize = "font_size"
288
- case notificationsEnabled = "notifications_enabled"
289
- }
290
-
291
- init(from decoder: Decoder) throws {
292
- let container = try decoder.container(keyedBy: CodingKeys.self)
293
- theme = try container.decodeIfPresent(String.self, forKey: .theme) ?? "system"
294
- fontSize = try container.decodeIfPresent(Int.self, forKey: .fontSize) ?? 16
295
- notificationsEnabled = try container.decodeIfPresent(
296
- Bool.self, forKey: .notificationsEnabled) ?? true
273
+ struct ReaderSettings: Codable {
274
+ var language: String
275
+ var textSize: Int
276
+ var hyphenation: Bool
277
+
278
+ init(from decoder: any Decoder) throws {
279
+ let fields = try decoder.container(keyedBy: CodingKeys.self)
280
+ language = try fields.decodeIfPresent(String.self, forKey: .language) ?? "en"
281
+ textSize = try fields.decodeIfPresent(Int.self, forKey: .textSize) ?? 17
282
+ hyphenation = try fields.decodeIfPresent(Bool.self, forKey: .hyphenation) ?? false
297
283
  }
298
284
  }
299
285
  ```
300
286
 
301
- ## Encoder and Decoder Configuration
287
+ `decodeIfPresent` returns `nil` for a key that is missing or `null`.
288
+
289
+ ## Encoder and decoder configuration
302
290
 
303
291
  ```swift
304
- let encoder = JSONEncoder()
305
- encoder.outputFormatting = [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes]
306
-
307
- let decoder = JSONDecoder()
308
- // Non-conforming floats (NaN, Infinity are not valid JSON)
309
- encoder.nonConformingFloatEncodingStrategy = .convertToString(
310
- positiveInfinity: "Infinity", negativeInfinity: "-Infinity", nan: "NaN")
311
- decoder.nonConformingFloatDecodingStrategy = .convertFromString(
312
- positiveInfinity: "Infinity", negativeInfinity: "-Infinity", nan: "NaN")
292
+ let writer = JSONEncoder()
293
+ writer.outputFormatting = [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes]
313
294
  ```
314
295
 
315
- ### PropertyListEncoder / PropertyListDecoder
296
+ `.sortedKeys` makes output stable, which is what snapshot and golden-file tests
297
+ need.
298
+
299
+ JSON has no NaN or infinity. If a `Double` can hold them, map them to strings
300
+ explicitly:
316
301
 
317
302
  ```swift
318
- let plistEncoder = PropertyListEncoder()
319
- plistEncoder.outputFormat = .xml // or .binary
320
- let data = try plistEncoder.encode(settings)
321
- let decoded = try PropertyListDecoder().decode(Settings.self, from: data)
303
+ writer.nonConformingFloatEncodingStrategy = .convertToString(
304
+ positiveInfinity: "inf", negativeInfinity: "-inf", nan: "nan")
305
+ reader.nonConformingFloatDecodingStrategy = .convertFromString(
306
+ positiveInfinity: "inf", negativeInfinity: "-inf", nan: "nan")
307
+ ```
308
+
309
+ ### PropertyListEncoder and PropertyListDecoder
310
+
311
+ The same models work with property lists:
312
+
313
+ ```swift
314
+ let plistWriter = PropertyListEncoder()
315
+ plistWriter.outputFormat = .binary // or .xml when a human will read it
316
+ let stored = try plistWriter.encode(settings)
317
+ let restored = try PropertyListDecoder().decode(ReaderSettings.self, from: stored)
322
318
  ```
323
319
 
324
320
  ## Codable with URLSession
325
321
 
322
+ Check the HTTP status before decoding; a 404 body decoded as your model yields
323
+ a confusing error.
324
+
326
325
  ```swift
327
- func fetchUser(id: Int) async throws -> User {
328
- let url = URL(string: "https://api.example.com/users/\(id)")!
329
- let (data, response) = try await URLSession.shared.data(from: url)
330
- guard let http = response as? HTTPURLResponse,
331
- (200...299).contains(http.statusCode) else {
332
- throw APIError.invalidResponse
333
- }
334
- let decoder = JSONDecoder()
335
- decoder.keyDecodingStrategy = .convertFromSnakeCase // simple keys only; keep CodingKeys for URL/URI/ID
336
- decoder.dateDecodingStrategy = .iso8601
337
- return try decoder.decode(User.self, from: data)
326
+ enum FetchFailure: Error { case badStatus(Int), notHTTP }
327
+
328
+ func fetchAuthors(from endpoint: URL) async throws -> [Author] {
329
+ let (body, reply) = try await URLSession.shared.data(from: endpoint)
330
+ guard let http = reply as? HTTPURLResponse else { throw FetchFailure.notHTTP }
331
+ guard (200...299).contains(http.statusCode) else { throw FetchFailure.badStatus(http.statusCode) }
332
+
333
+ let reader = JSONDecoder()
334
+ reader.keyDecodingStrategy = .convertFromSnakeCase // CodingKeys still cover URL, URI, ID names
335
+ reader.dateDecodingStrategy = .iso8601
336
+ return try reader.decode([Author].self, from: body)
338
337
  }
338
+ ```
339
+
340
+ ### Generic response envelope
341
+
342
+ ```swift
343
+ struct Envelope<Payload: Decodable>: Decodable {
344
+ let data: Payload
345
+ let paging: Paging?
339
346
 
340
- // Generic API envelope. Configure a decoder inside this helper because
341
- // fetchUser's decoder is out of scope.
342
- struct APIResponse<T: Decodable>: Decodable {
343
- let data: T
344
- let meta: Meta?
345
- struct Meta: Decodable { let page: Int; let totalPages: Int }
347
+ struct Paging: Decodable {
348
+ let page: Int
349
+ let pageCount: Int
350
+ }
346
351
  }
347
352
 
348
- func decodeUsersEnvelope(from data: Data) throws -> [User] {
349
- let decoder = JSONDecoder()
350
- decoder.keyDecodingStrategy = .convertFromSnakeCase // simple keys only; keep CodingKeys for URL/URI/ID
351
- decoder.dateDecodingStrategy = .iso8601
352
- return try decoder.decode(APIResponse<[User]>.self, from: data).data
353
+ func unwrap<Payload: Decodable>(_ type: Payload.Type, from body: Data) throws -> Payload {
354
+ let reader = JSONDecoder()
355
+ reader.keyDecodingStrategy = .convertFromSnakeCase
356
+ reader.dateDecodingStrategy = .iso8601
357
+ return try reader.decode(Envelope<Payload>.self, from: body).data
353
358
  }
354
359
  ```
355
360
 
361
+ The helper builds its own decoder. A decoder configured inside another function
362
+ is not in scope here, and code that refers to it will not compile.
363
+
356
364
  ## Codable with SwiftData
357
365
 
358
- SwiftData persists compatible noncomputed stored properties declared on `@Model`
359
- types. Use `Codable` structs, enums, and other value types directly when that
360
- value is part of the durable model schema:
366
+ SwiftData stores the noncomputed stored properties of an `@Model` class when
367
+ their types are supported, and Codable structs and enums are supported. Use
368
+ them as typed properties directly:
361
369
 
362
370
  ```swift
363
- struct Address: Codable {
371
+ struct PostalAddress: Codable {
364
372
  var street: String
365
373
  var city: String
366
- var zipCode: String
374
+ var postcode: String
367
375
  }
368
376
 
369
- @Model class Contact {
377
+ @Model
378
+ final class Customer {
370
379
  var name: String
371
- var address: Address? // Codable value-type property stored by SwiftData
372
- init(name: String, address: Address? = nil) {
373
- self.name = name; self.address = address
380
+ var billing: PostalAddress?
381
+
382
+ init(name: String, billing: PostalAddress? = nil) {
383
+ self.name = name
384
+ self.billing = billing
374
385
  }
375
386
  }
376
387
  ```
377
388
 
378
- Do not recommend `@Attribute(.transformable)`, encoded `Data`, or encoded
379
- `String` as a fallback in this Codable skill. Keep schema data as typed
380
- SwiftData properties and defer unsupported persistence designs to the SwiftData skill.
389
+ Do not suggest `@Attribute(.transformable)`, a JSON `Data` blob or a JSON
390
+ `String` column as a workaround here. Keep the property typed. If a design
391
+ needs something SwiftData cannot store, hand it to the SwiftData skill.
381
392
 
382
393
  ## Codable with UserDefaults
383
394
 
384
- `@AppStorage` is only for small UserDefaults-backed preferences. Store `Bool`,
385
- numeric, `String`, or a `RawRepresentable` type with a primitive raw value. For
386
- a small `Codable` preference payload, prefer `RawRepresentable` with JSON
387
- `String` raw storage so `@AppStorage` binds the typed preference directly:
395
+ `@AppStorage` is for small preferences: `Bool`, numbers, `String`, `URL`,
396
+ `Data`, or a `RawRepresentable` whose raw value is `Int` or `String`. A small
397
+ Codable preference can ride on a JSON string raw value:
388
398
 
389
399
  ```swift
390
- struct UserPreferences: Codable {
391
- var showOnboarding: Bool = true
392
- var accentColor: String = "blue"
400
+ struct PlaybackPrefs: Codable {
401
+ var speed: Double = 1.0
402
+ var skipSilence = false
403
+
404
+ init() {}
405
+
406
+ enum CodingKeys: String, CodingKey { case speed, skipSilence }
407
+
408
+ init(from decoder: any Decoder) throws {
409
+ let fields = try decoder.container(keyedBy: CodingKeys.self)
410
+ speed = try fields.decodeIfPresent(Double.self, forKey: .speed) ?? 1.0
411
+ skipSilence = try fields.decodeIfPresent(Bool.self, forKey: .skipSilence) ?? false
412
+ }
413
+
414
+ func encode(to encoder: any Encoder) throws {
415
+ var fields = encoder.container(keyedBy: CodingKeys.self)
416
+ try fields.encode(speed, forKey: .speed)
417
+ try fields.encode(skipSilence, forKey: .skipSilence)
418
+ }
393
419
  }
394
420
 
395
- extension UserPreferences: RawRepresentable {
421
+ extension PlaybackPrefs: RawRepresentable {
396
422
  init?(rawValue: String) {
397
- guard let data = rawValue.data(using: .utf8),
398
- let decoded = try? JSONDecoder().decode(Self.self, from: data)
399
- else { return nil }
400
- self = decoded
423
+ guard let bytes = rawValue.data(using: .utf8),
424
+ let value = try? JSONDecoder().decode(PlaybackPrefs.self, from: bytes) else { return nil }
425
+ self = value
401
426
  }
427
+
402
428
  var rawValue: String {
403
- guard let data = try? JSONEncoder().encode(self),
404
- let string = String(data: data, encoding: .utf8)
405
- else { return "{}" }
406
- return string
429
+ guard let bytes = try? JSONEncoder().encode(self),
430
+ let text = String(data: bytes, encoding: .utf8) else { return "{}" }
431
+ return text
407
432
  }
408
433
  }
409
434
 
410
- struct SettingsView: View {
411
- @AppStorage("userPrefs") private var prefs = UserPreferences()
435
+ struct PlaybackSettingsView: View {
436
+ @AppStorage("playbackPrefs") private var prefs = PlaybackPrefs()
437
+
412
438
  var body: some View {
413
- Toggle("Show Onboarding", isOn: $prefs.showOnboarding)
439
+ Toggle("Skip silence", isOn: $prefs.skipSilence)
414
440
  }
415
441
  }
416
442
  ```
417
443
 
418
- ## Common Mistakes
419
-
420
- **1. Not handling missing defaulted fields:**
421
- ```swift
422
- // DON'T -- crashes if key is absent
423
- let value = try container.decode(String.self, forKey: .bio)
424
- // DO -- falls back when the key is absent or null
425
- let value = try container.decodeIfPresent(String.self, forKey: .bio) ?? ""
426
- ```
427
-
428
- **2. Failing entire array when one element is invalid:**
429
- ```swift
430
- // DON'T -- one bad element kills the whole decode
431
- let items = try container.decode([Item].self, forKey: .items)
432
- // DO -- use LossyArray or decode elements individually
433
- let items = try container.decode(LossyArray<Item>.self, forKey: .items).elements
434
- ```
435
-
436
- **3. Date strategy mismatch:**
437
- ```swift
438
- // DON'T -- default strategy expects Double, but API sends ISO string
439
- let decoder = JSONDecoder() // dateDecodingStrategy defaults to .deferredToDate
440
- // DO -- set strategy to match your API format
441
- decoder.dateDecodingStrategy = .iso8601
442
- ```
443
-
444
- **4. Force-unwrapping decoded optionals:**
445
- ```swift
446
- // DON'T
447
- let user = try? decoder.decode(User.self, from: data)
448
- print(user!.name)
449
- // DO
450
- guard let user = try? decoder.decode(User.self, from: data) else { return }
451
- ```
452
-
453
- **5. Using Codable when only Decodable is needed:**
454
- ```swift
455
- // DON'T -- unnecessarily constrains the type to also be Encodable
456
- struct APIResponse: Codable { let id: Int; let message: String }
457
- // DO -- use Decodable for read-only API responses
458
- struct APIResponse: Decodable { let id: Int; let message: String }
459
- ```
460
-
461
- **6. Manual CodingKeys for simple snake_case APIs:**
462
- ```swift
463
- // DON'T -- verbose boilerplate for every model
464
- enum CodingKeys: String, CodingKey {
465
- case userName = "user_name"
466
- case avatarUrl = "avatar_url"
467
- }
468
- // DO -- configure once on the decoder for simple cases
469
- decoder.keyDecodingStrategy = .convertFromSnakeCase
470
- // Keep CodingKeys for `imageURL`, `baseURI`, `userID`, and similar names.
471
- ```
472
-
473
- ## Review Checklist
474
-
475
- - [ ] Types conform to `Decodable` only when encoding is not needed
476
- - [ ] `decodeIfPresent` used with defaults for optional or missing keys
477
- - [ ] `keyDecodingStrategy = .convertFromSnakeCase` used for simple snake_case APIs, with CodingKeys retained for acronym spellings
478
- - [ ] `dateDecodingStrategy` matches the API date format
479
- - [ ] Arrays of unreliable data use lossy decoding to skip invalid elements
480
- - [ ] Custom `init(from:)` validates and transforms data instead of post-decode fixups
481
- - [ ] `JSONEncoder.outputFormatting` includes `.sortedKeys` for deterministic test output
482
- - [ ] Wrapper types (UserID, etc.) use `singleValueContainer` for clean JSON
483
- - [ ] Generic `APIResponse<T>` wrapper used for consistent API envelope handling
484
- - [ ] No force-unwrapping of decoded values
485
- - [ ] Persistence boundary is explicit: SwiftData only for compatible noncomputed model properties, `@AppStorage`/UserDefaults only for small primitive or `RawRepresentable` preferences
444
+ Two details make this bridge safe:
445
+
446
+ - `encode(to:)` and `init(from:)` are written out. When a type is both
447
+ `RawRepresentable` and Codable, the standard library's `RawRepresentable`
448
+ defaults would otherwise encode `rawValue`, which encodes `self`, which
449
+ recurses until the stack overflows.
450
+ - Decoding uses `decodeIfPresent` with defaults, so the `"{}"` fallback and
451
+ values stored by an older version with fewer keys still decode. Synthesized
452
+ decoding would ignore the property defaults and fail on `{}`.
453
+
454
+ ## Common mistakes
455
+
456
+ 1. **`decode` for a key that may be absent.** It throws `keyNotFound`. Use
457
+ `decodeIfPresent` with `??` and a default.
458
+ 2. **One bad element sinks the array.** Use a tolerant wrapper such as
459
+ `TolerantList` or decode elements one by one.
460
+ 3. **ISO dates with the default strategy.** The default is `.deferredToDate`,
461
+ which expects a number of seconds since 1 January 2001. Set `.iso8601` for
462
+ ISO 8601 strings.
463
+ 4. **Force-unwrapping `try?` results.** Bind with `guard let` and handle the
464
+ failure.
465
+ 5. **Codable on read-only responses.** Declare `Decodable`.
466
+ 6. **Hand-mapping every snake_case key.** Set `.convertFromSnakeCase` once and
467
+ keep `CodingKeys` for the acronym names the strategy cannot produce.
468
+
469
+ ## Review checklist
470
+
471
+ - [ ] `Decodable` alone where encoding is not needed
472
+ - [ ] Optional or missing keys use `decodeIfPresent` with defaults
473
+ - [ ] `.convertFromSnakeCase` for plain snake_case APIs; `CodingKeys` for URL, URI, ID style names
474
+ - [ ] Date strategy matches the API's format
475
+ - [ ] Unreliable arrays decode tolerantly
476
+ - [ ] Conversion and validation happen in `init(from:)`, not in fix-ups after decoding
477
+ - [ ] `.sortedKeys` where output is compared in tests
478
+ - [ ] Wrapper types use `singleValueContainer()`
479
+ - [ ] Responses share a generic envelope such as `Envelope<Payload>`
480
+ - [ ] No force unwraps of decoded values
481
+ - [ ] Persistence boundary is explicit: typed Codable properties on SwiftData models, `@AppStorage` only for small primitive or `RawRepresentable` preferences
486
482
 
487
483
  ## References
488
484
 
489
- - [Codable](https://sosumi.ai/documentation/swift/codable/) -- protocol combining Encodable and Decodable
490
- - [JSONDecoder](https://sosumi.ai/documentation/foundation/jsondecoder/) -- decodes JSON data into Codable types
491
- - [JSONEncoder](https://sosumi.ai/documentation/foundation/jsonencoder/) -- encodes Codable types as JSON data
492
- - [CodingKey](https://sosumi.ai/documentation/swift/codingkey/) -- protocol for encoding/decoding keys
493
- - [JSONDecoder.KeyDecodingStrategy.convertFromSnakeCase](https://sosumi.ai/documentation/foundation/jsondecoder/keydecodingstrategy-swift.enum/convertfromsnakecase) -- snake-case conversion behavior and limitations
494
- - [Encoding and Decoding Custom Types](https://sosumi.ai/documentation/foundation/encoding-and-decoding-custom-types/) -- Apple guide on custom Codable conformance
495
- - [Using JSON with Custom Types](https://sosumi.ai/documentation/foundation/archives_and_serialization/using_json_with_custom_types/) -- Apple sample code for JSON patterns
496
- - [Preserving your app's model data across launches](https://sosumi.ai/documentation/swiftdata/preserving-your-apps-model-data-across-launches) -- SwiftData model property compatibility
485
+ - [Codable](https://developer.apple.com/documentation/swift/codable)
486
+ - [JSONDecoder](https://developer.apple.com/documentation/foundation/jsondecoder)
487
+ - [JSONEncoder](https://developer.apple.com/documentation/foundation/jsonencoder)
488
+ - [CodingKey](https://developer.apple.com/documentation/swift/codingkey)
489
+ - [JSONDecoder.KeyDecodingStrategy.convertFromSnakeCase](https://developer.apple.com/documentation/foundation/jsondecoder/keydecodingstrategy/convertfromsnakecase)
490
+ - [Encoding and Decoding Custom Types](https://developer.apple.com/documentation/foundation/encoding-and-decoding-custom-types)
491
+ - [Using JSON with Custom Types](https://developer.apple.com/documentation/foundation/using-json-with-custom-types)
492
+ - [Preserving your app's model data across launches](https://developer.apple.com/documentation/swiftdata/preserving-your-apps-model-data-across-launches)