@mmerterden/multi-agent-pipeline 20.7.0 → 20.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +0 -10
  3. package/docs/facts.json +1 -1
  4. package/manifest.json +266 -267
  5. package/package.json +2 -2
  6. package/pipeline/scripts/_notices.mjs +1 -1
  7. package/pipeline/skills/.skill-manifest.json +68 -68
  8. package/pipeline/skills/shared/README.md +70 -70
  9. package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
  10. package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
  11. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
  12. package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
  13. package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
  14. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
  15. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
  16. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
  17. package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
  18. package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
  19. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
  20. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
  21. package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
  22. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
  23. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
  24. package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
  25. package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
  26. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
  27. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
  28. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
  29. package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
  30. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
  31. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
  32. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
  33. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
  34. package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
  35. package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
  36. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
  37. package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
  38. package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
  39. package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
  40. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
  41. package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
  42. package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
  43. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
  44. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
  45. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
  46. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
  47. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
  48. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
  49. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
  50. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
  51. package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
  52. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
  53. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
  54. package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
  55. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
  56. package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
  57. package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
  58. package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
  59. package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
  60. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
  61. package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
  62. package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
  63. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
  64. package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
  65. package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
  66. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
  67. package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
  68. package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
  69. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
  70. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
  71. package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
  72. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
  73. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
  74. package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
  75. package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
  76. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
  77. package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
  78. package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
  79. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
  80. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
  81. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
  82. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
  83. package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
  84. package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
  85. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
  86. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
  87. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
  88. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
  89. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
  90. package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
  91. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
  92. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
  93. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
  94. package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
  95. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
  96. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
  97. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
  98. package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
  99. package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
  100. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
  101. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
  102. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
  103. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
  104. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
  105. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
  106. package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
  107. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
  108. package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
  109. package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
  110. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
  111. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
  112. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
  113. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
  114. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
  115. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
  116. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
  117. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
  118. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
  119. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
  120. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
  121. package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
  122. package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
  123. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
  124. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
  125. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
  126. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
  127. package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
  128. package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
  129. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
  130. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
  131. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
  132. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
  133. package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
  134. package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
  135. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
  136. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
  137. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
  138. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
  139. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
  140. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
  141. package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
  142. package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
  143. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
  144. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
  145. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
  146. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
  147. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
  148. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
  149. package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
  150. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
  151. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
  152. package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
  153. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
  154. package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
  155. package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
  156. package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
  157. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
  158. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
  159. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
  160. package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
  161. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
  162. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
  163. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
  164. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
  165. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
  166. package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
  167. package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
  168. package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
  169. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
  170. package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
  171. package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
  172. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
  173. package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
  174. package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
  175. package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
  176. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
  177. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
  178. package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
  179. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
  180. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
  181. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
  182. package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
  183. package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
  184. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
  185. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
  186. package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
  187. package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
  188. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
  189. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
  190. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
  191. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
  192. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
  193. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
  194. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
  195. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
  196. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
  197. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
  198. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
  199. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
  200. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
  201. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
  202. package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
  203. package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
  204. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
  205. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
  206. package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
  207. package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
  208. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
  209. package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
  210. package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
  211. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
  212. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
  213. package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
  214. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
  215. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
  216. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
  217. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
  218. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
  219. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
  220. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
  221. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
  222. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
  223. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
  224. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
  225. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
  226. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
  227. package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
  228. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
  229. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
  230. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
  231. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
  232. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
  233. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
  234. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
  235. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
  236. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
  237. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
  238. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
  239. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
  240. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
  241. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
  242. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
  243. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
  244. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
  245. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
  246. package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
  247. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
  248. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
  249. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
  250. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
  251. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
  252. package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
  253. package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
  254. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
  255. package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
  256. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
  257. package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
  258. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
  259. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
  260. package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
  261. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
  262. package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
  263. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
  264. package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
@@ -1,500 +1,484 @@
1
1
  ---
2
2
  name: swift-architecture
3
- description: "Select, implement, or migrate between app architecture patterns for Apple platform apps. Use when choosing between MV (Model-View with @Observable), MVVM, MVI, TCA (The Composable Architecture), Clean Architecture, VIPER, or Coordinator patterns; when evaluating architecture fit for a feature's complexity; when migrating from one pattern to another; or when reviewing whether an app's current architecture is appropriate. Scoped to Apple-platform patterns using Swift 6.3, SwiftUI, and UIKit."
3
+ description: "App architecture on Apple platforms (Swift 6.3, SwiftUI, UIKit): MV with @Observable, MVVM, MVI, TCA, Clean Architecture, VIPER, Coordinator, module boundaries, dependency direction. Use when selecting, implementing or migrating between patterns, judging whether a feature's complexity justifies a heavier one, or reviewing whether an app's architecture fits. Not for SwiftUI state wrappers, navigation, concurrency diagnostics or test syntax."
4
4
  metadata:
5
- source: "dpearson2699/swift-ios-skills (PolyForm Perimeter 1.0.0)"
5
+ source: multi-agent-pipeline
6
6
  ---
7
+
7
8
  # Swift Architecture
8
9
 
9
- Select and implement the right architecture pattern for Apple platform apps built with Swift 6.3 and SwiftUI or UIKit.
10
-
11
- ## Contents
12
-
13
- - [Scope Boundary](#scope-boundary)
14
- - [Architecture Selection](#architecture-selection)
15
- - [MV Pattern (Model-View with `@Observable`)](#mv-pattern)
16
- - [MVVM](#mvvm)
17
- - [MVI (Model-View-Intent)](#mvi)
18
- - [TCA (The Composable Architecture)](#tca)
19
- - [Clean Architecture](#clean-architecture)
20
- - [Coordinator Pattern](#coordinator-pattern)
21
- - [VIPER](#viper)
22
- - [Migration Between Patterns](#migration-between-patterns)
23
- - [Common Mistakes](#common-mistakes)
24
- - [Review Checklist](#review-checklist)
25
- - [References](#references)
26
-
27
- ## Scope Boundary
28
-
29
- This skill owns architecture-level decisions: pattern selection, module
30
- boundaries, dependency direction, migration/escalation strategy, and structural
31
- test strategy. It does not own SwiftUI state mechanics; route `@State`,
32
- `@Bindable`, `@Environment`, edit-sheet/local state, bindings, view composition,
33
- and `@Observable` MV implementation mechanics to `swiftui-patterns`. Use
34
- `swiftui-navigation` for `NavigationStack`, `NavigationSplitView`,
35
- `NavigationPath`, route models, sheets, tabs, and deep-link URL handling;
36
- `swift-concurrency` for `@MainActor`, default MainActor isolation, `Sendable`,
37
- strict-concurrency diagnostics, and data-race diagnostics; and `swift-testing`
38
- for `@Test`, `#expect`, `#require`, fixtures, parameterized tests, mocks, stubs,
39
- and suite organization.
40
-
41
- ## Architecture Selection
42
-
43
- | Pattern | Best For | Complexity | Testability |
44
- |---------|----------|-----------|-------------|
45
- | **MV** | Small-to-medium SwiftUI apps, rapid iteration | Low | Moderate |
46
- | **MVVM** | Medium apps, teams familiar with reactive patterns | Medium | High |
47
- | **MVI** | Complex state machines, predictable state flow | Medium-High | High |
48
- | **TCA** | Large apps needing composable features, strong testing | High | Very High |
49
- | **Clean Architecture** | Enterprise apps, strict separation of concerns | High | Very High |
50
- | **Coordinator** | Apps with complex navigation flows (UIKit or hybrid) | Medium | High |
51
- | **VIPER** | Legacy UIKit modules already using VIPER boundaries | Very High | High |
52
-
53
- **Default recommendation for new SwiftUI apps:** Start with MV (Model-View
54
- with `@Observable`). Escalate to MVVM or TCA only when the feature's complexity
55
- demands it.
56
-
57
- Boundary-split answers should use one `swift-architecture` bucket for
58
- pattern/module/dependency/migration/test-strategy decisions. Do not add a
59
- separate architecture-owned "SwiftUI state ownership" bucket; property-wrapper,
60
- local binding, navigation, concurrency-diagnostic, fixture, and parameterized
61
- test mechanics are sibling-skill handoffs.
62
-
63
- ### Decision Framework
64
-
65
- 1. **Is the feature a simple CRUD screen?** → MV pattern
66
- 2. **Does the screen have complex business logic separate from the view?** → MVVM
67
- 3. **Do you need deterministic state transitions and side-effect management?** → MVI or TCA
68
- 4. **Is the app large with many independent feature modules?** → TCA or Clean Architecture
69
- 5. **Is navigation complex with deep linking and conditional flows?** → Add Coordinator pattern
70
-
71
- ## MV Pattern
72
-
73
- The simplest SwiftUI architecture. The view observes `@Observable` models
74
- directly. No intermediate view model layer.
10
+ Which structure a Swift app or feature should have, and how to move from one
11
+ structure to another without a rewrite. Examples are written for Swift 6.3; they use
12
+ SwiftUI with Observation (iOS 17 or later) unless a UIKit type appears.
75
13
 
76
- ```swift
77
- import Observation
78
- import SwiftUI
14
+ ## Scope boundary
15
+
16
+ This skill decides:
17
+
18
+ - which pattern a feature uses,
19
+ - where module boundaries fall and which way dependencies point,
20
+ - how to migrate or escalate between patterns,
21
+ - the test strategy at the structural level (what is unit-testable, and where).
22
+
23
+ It hands off the mechanics:
24
+
25
+ | Topic | Skill |
26
+ | --- | --- |
27
+ | `@State`, `@Bindable`, `@Environment`, bindings, local and edit-sheet state, view composition, the mechanics of MV with `@Observable` | `swiftui-patterns` |
28
+ | `NavigationStack`, `NavigationSplitView`, paths, route models, sheets, tabs, deep links | `swiftui-navigation` |
29
+ | `@MainActor`, default main-actor isolation, `Sendable`, strict-concurrency and data-race diagnostics | `swift-concurrency` |
30
+ | Swift Testing syntax (`@Test`, `#expect`, `#require`), fixtures, parameterized cases, test doubles, suite layout | `swift-testing` |
31
+
32
+ When one question touches several of these, answer the architecture part in a
33
+ single block (pattern, modules, dependency direction, migration, test
34
+ strategy). There is no extra block here for how SwiftUI views hold state;
35
+ property wrappers, bindings, navigation wiring, concurrency errors, fixtures and
36
+ parameterized tests are answered by the skills in the table.
37
+
38
+ ## Architecture selection
39
+
40
+ | Pattern | Good match | Cost to adopt | How testable |
41
+ | --- | --- | --- | --- |
42
+ | MV (`@Observable` models) | Small or mid-sized SwiftUI apps where speed of change matters | Low | Moderate |
43
+ | MVVM | Mid-sized apps; teams at home with reactive view models | Medium | High |
44
+ | MVI | Features that are really state machines and need a predictable flow | Medium to high | High |
45
+ | TCA | Big apps built from composable features, with heavy testing | High | Very high |
46
+ | Clean Architecture | Enterprise codebases that require strict layer separation | High | Very high |
47
+ | Coordinator | Involved navigation in UIKit or mixed UIKit and SwiftUI apps | Medium | High |
48
+ | VIPER | UIKit modules that were already built as VIPER | Very high | High |
79
49
 
50
+ Default for a new SwiftUI app: MV with `@Observable`. Escalate to MVVM, and
51
+ later TCA, when the feature has grown enough to need it, not before.
52
+
53
+ ### Decision framework
54
+
55
+ - A simple CRUD screen: **MV**.
56
+ - Business logic that should live apart from the view: **MVVM**.
57
+ - State transitions that must be deterministic, with side effects managed
58
+ explicitly: **MVI** or **TCA**.
59
+ - Dozens of separately owned feature modules in one app: **TCA** or **Clean
60
+ Architecture**.
61
+ - Navigation with deep links and conditional flows: add a **Coordinator** on
62
+ top of whichever pattern the features use (in pure SwiftUI, prefer path-based
63
+ routing first; see Coordinator below).
64
+
65
+ ## MV pattern
66
+
67
+ Views read `@Observable` model objects themselves; nothing sits between them.
68
+
69
+ ```swift
80
70
  @MainActor
81
71
  @Observable
82
- final class TripStore {
83
- var trips: [Trip] = []
84
- var isLoading = false
85
- var error: Error?
72
+ final class WorkoutLog {
73
+ private(set) var sessions: [Workout] = []
74
+ private(set) var loading = false
75
+ var lastError: (any Error)?
86
76
 
87
- private let service: TripService
77
+ private let store: any WorkoutStore
88
78
 
89
- init(service: TripService) {
90
- self.service = service
91
- }
79
+ init(store: any WorkoutStore) { self.store = store }
92
80
 
93
- func loadTrips() async {
94
- isLoading = true
95
- defer { isLoading = false }
96
- do {
97
- trips = try await service.fetchTrips()
98
- } catch {
99
- self.error = error
100
- }
81
+ func refresh() async {
82
+ loading = true
83
+ defer { loading = false }
84
+ do { sessions = try await store.allWorkouts() }
85
+ catch { lastError = error }
101
86
  }
102
87
 
103
- func deleteTrip(_ trip: Trip) async throws {
104
- try await service.delete(trip)
105
- trips.removeAll { $0.id == trip.id }
88
+ func remove(_ workout: Workout) async throws {
89
+ try await store.delete(workout.id)
90
+ sessions.removeAll { $0.id == workout.id }
106
91
  }
107
92
  }
108
93
 
109
- struct TripsView: View {
110
- @State private var store = TripStore(service: .live)
94
+ struct WorkoutLogScreen: View {
95
+ @State private var log: WorkoutLog
96
+
97
+ init(store: any WorkoutStore) {
98
+ _log = State(initialValue: WorkoutLog(store: store))
99
+ }
111
100
 
112
101
  var body: some View {
113
- List(store.trips) { trip in
114
- TripRow(trip: trip)
115
- }
116
- .task { await store.loadTrips() }
102
+ let entries = log.sessions
103
+ return List(entries) { Text($0.title) }
104
+ .overlay { if log.loading { ProgressView() } }
105
+ .task { await log.refresh() }
117
106
  }
118
107
  }
119
108
  ```
120
109
 
121
- **When MV is enough:** Single-screen features, prototype/MVP, small teams,
122
- straightforward data flow.
110
+ The view owns the model with `@State` and loads it in `.task`. The dependency
111
+ comes in through the initializer, never created inside the model.
123
112
 
124
- **When to upgrade:** Business logic grows complex, unit testing the view's
125
- behavior becomes difficult, multiple views need to share and transform the
126
- same state differently.
113
+ MV is enough for single-screen features, prototypes and MVPs, small teams and
114
+ straightforward data flow. Move on when business logic keeps growing, when view
115
+ behaviour becomes hard to unit test, or when several views need different
116
+ transformations of the same shared state.
127
117
 
128
118
  ## MVVM
129
119
 
130
- Separates view logic into a `ViewModel` that the view observes. The view model
131
- transforms model data for display and handles user actions.
120
+ A view model sits between the view and the model: it prepares display values
121
+ and receives user actions, and the view watches it.
132
122
 
133
123
  ```swift
124
+ struct WorkoutRow: Identifiable {
125
+ let id: Workout.ID
126
+ let title: String
127
+ let when: String
128
+
129
+ init(_ workout: Workout) {
130
+ id = workout.id
131
+ title = workout.title
132
+ when = workout.start.formatted(.dateTime.month().day())
133
+ }
134
+ }
135
+
134
136
  @MainActor
135
137
  @Observable
136
- final class TripListViewModel {
137
- private(set) var trips: [TripRowItem] = []
138
- private(set) var isLoading = false
139
- var searchText = ""
140
-
141
- var filteredTrips: [TripRowItem] {
142
- guard !searchText.isEmpty else { return trips }
143
- return trips.filter { $0.name.localizedStandardContains(searchText) }
144
- }
138
+ final class WorkoutListModel {
139
+ private(set) var rows: [WorkoutRow] = []
140
+ private(set) var loading = false
141
+ var filter = ""
145
142
 
146
- private let repository: TripRepository
143
+ private var all: [Workout] = []
144
+ private let store: any WorkoutStore
147
145
 
148
- init(repository: TripRepository) {
149
- self.repository = repository
150
- }
146
+ init(store: any WorkoutStore) { self.store = store }
151
147
 
152
- func loadTrips() async {
153
- isLoading = true
154
- defer { isLoading = false }
155
- let models = (try? await repository.fetchAll()) ?? []
156
- trips = models.map { TripRowItem(from: $0) }
148
+ var visibleRows: [WorkoutRow] {
149
+ filter.isEmpty ? rows : rows.filter { $0.title.localizedStandardContains(filter) }
157
150
  }
158
151
 
159
- func delete(at offsets: IndexSet) async {
160
- let toDelete = offsets.map { filteredTrips[$0] }
161
- for item in toDelete {
162
- try? await repository.delete(id: item.id)
163
- }
164
- await loadTrips()
152
+ func load() async {
153
+ loading = true
154
+ defer { loading = false }
155
+ all = (try? await store.allWorkouts()) ?? []
156
+ rows = all.map(WorkoutRow.init)
165
157
  }
166
- }
167
-
168
- struct TripRowItem: Identifiable {
169
- let id: UUID
170
- let name: String
171
- let dateRange: String
172
158
 
173
- init(from trip: Trip) {
174
- self.id = trip.id
175
- self.name = trip.name
176
- self.dateRange = trip.startDate.formatted(.dateTime.month().day())
177
- + " - " + trip.endDate.formatted(.dateTime.month().day())
159
+ func delete(rowsAt positions: IndexSet) async {
160
+ let doomed = positions.map { visibleRows[$0].id }
161
+ for id in doomed { try? await store.delete(id) }
162
+ await load()
178
163
  }
179
164
  }
180
165
 
181
- struct TripListView: View {
182
- @State private var viewModel: TripListViewModel
166
+ struct WorkoutListScreen: View {
167
+ @State private var model: WorkoutListModel
183
168
 
184
- init(repository: TripRepository) {
185
- _viewModel = State(initialValue: TripListViewModel(repository: repository))
169
+ init(store: any WorkoutStore) {
170
+ _model = State(initialValue: WorkoutListModel(store: store))
186
171
  }
187
172
 
188
173
  var body: some View {
189
174
  List {
190
- ForEach(viewModel.filteredTrips) { item in
191
- Text(item.name)
192
- }
193
- .onDelete { offsets in
194
- Task { await viewModel.delete(at: offsets) }
175
+ ForEach(model.visibleRows, id: \.id) { row in
176
+ LabeledContent(row.title, value: row.when)
195
177
  }
178
+ .onDelete { positions in Task { await model.delete(rowsAt: positions) } }
196
179
  }
197
- .searchable(text: $viewModel.searchText)
198
- .task { await viewModel.loadTrips() }
180
+ .searchable(text: $model.filter)
181
+ .task { await model.load() }
199
182
  }
200
183
  }
201
184
  ```
202
185
 
203
- **Testing a ViewModel:**
186
+ The payoff is a view model you can test without any view:
204
187
 
205
188
  ```swift
206
- @Test func filteredTripsMatchesSearch() async {
207
- let repo = MockTripRepository(trips: [
208
- Trip(name: "Paris"), Trip(name: "Tokyo"), Trip(name: "Paris TX")
209
- ])
210
- let vm = TripListViewModel(repository: repo)
211
- await vm.loadTrips()
212
- vm.searchText = "Paris"
213
- #expect(vm.filteredTrips.count == 2)
189
+ @Test @MainActor
190
+ func filterMatchesTitle() async {
191
+ let model = WorkoutListModel(store: FakeWorkoutStore(titles: ["Row", "Run", "Swim"]))
192
+ await model.load()
193
+ model.filter = "ru"
194
+ #expect(model.visibleRows.count == 1)
214
195
  }
215
196
  ```
216
197
 
217
198
  ## MVI
218
199
 
219
- Unidirectional data flow: views dispatch **intents**, a **reducer** produces
220
- new **state**, and **side effects** are handled explicitly.
200
+ Data moves one way. The view emits intents, a single function applies each
201
+ intent to the state, and any work with side effects is spelled out there.
221
202
 
222
203
  ```swift
223
204
  @MainActor
224
205
  @Observable
225
- final class TripListStore {
226
- private(set) var state = State()
227
-
206
+ final class WorkoutFeed {
228
207
  struct State {
229
- var trips: [Trip] = []
230
- var isLoading = false
231
- var error: String?
208
+ var sessions: [Workout] = []
209
+ var loading = false
210
+ var message: String?
232
211
  }
233
212
 
234
213
  enum Intent {
235
- case loadTrips
236
- case deleteTrip(Trip)
237
- case clearError
214
+ case appeared
215
+ case remove(Workout.ID)
216
+ case dismissMessage
238
217
  }
239
218
 
240
- private let service: TripService
219
+ private(set) var state = State()
220
+ private let store: any WorkoutStore
241
221
 
242
- init(service: TripService) {
243
- self.service = service
244
- }
222
+ init(store: any WorkoutStore) { self.store = store }
245
223
 
246
224
  func send(_ intent: Intent) {
247
- Task { await handle(intent) }
225
+ Task { await apply(intent) }
248
226
  }
249
227
 
250
- private func handle(_ intent: Intent) async {
251
- switch intent {
252
- case .loadTrips:
253
- state.isLoading = true
228
+ private func apply(_ next: Intent) async {
229
+ switch next {
230
+ case .appeared:
231
+ state.loading = true
232
+ do { state.sessions = try await store.allWorkouts() }
233
+ catch { state.message = error.localizedDescription }
234
+ state.loading = false
235
+ case .remove(let id):
254
236
  do {
255
- state.trips = try await service.fetchTrips()
237
+ try await store.delete(id)
238
+ state.sessions.removeAll { $0.id == id }
256
239
  } catch {
257
- state.error = error.localizedDescription
240
+ state.message = error.localizedDescription
258
241
  }
259
- state.isLoading = false
260
-
261
- case .deleteTrip(let trip):
262
- try? await service.delete(trip)
263
- state.trips.removeAll { $0.id == trip.id }
264
-
265
- case .clearError:
266
- state.error = nil
242
+ case .dismissMessage:
243
+ state.message = nil
267
244
  }
268
245
  }
269
246
  }
270
247
  ```
271
248
 
272
- **Advantages:** Predictable state transitions, easy to log/replay intents,
273
- clear separation of "what happened" from "what changed."
249
+ What it buys: every transition is predictable, intents can be logged and
250
+ replayed, and "what happened" (the intent) is kept apart from "what changed"
251
+ (the state).
274
252
 
275
253
  ## TCA
276
254
 
277
- The Composable Architecture (Point-Free) provides composable reducers,
278
- dependency injection, exhaustive testing, and structured side effects.
279
-
280
- Docs: [TCA](https://sosumi.ai/external/https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture)
255
+ The Composable Architecture is an open-source library. Features are reducers
256
+ that compose; dependencies are injected through a registry; effects are values
257
+ the store runs; tests can assert every state change exhaustively.
281
258
 
282
259
  ```swift
283
260
  import ComposableArchitecture
284
261
 
285
262
  @Reducer
286
- struct TripList {
263
+ struct WorkoutsFeature {
287
264
  @ObservableState
288
265
  struct State: Equatable {
289
- var trips: IdentifiedArrayOf<Trip> = []
290
- var isLoading = false
291
- var errorMessage: String?
266
+ var sessions: IdentifiedArrayOf<Workout> = []
267
+ var loading = false
292
268
  }
293
269
 
294
270
  enum Action {
295
271
  case onAppear
296
- case tripsLoaded([Trip])
297
- case tripsFailed(String)
298
- case deleteTrip(Trip.ID)
272
+ case loaded([Workout])
273
+ case removeTapped(Workout.ID)
299
274
  }
300
275
 
301
- @Dependency(\.tripClient) var tripClient
276
+ @Dependency(\.workoutClient) var workoutClient
302
277
 
303
278
  var body: some ReducerOf<Self> {
304
279
  Reduce { state, action in
305
280
  switch action {
306
- case .onAppear:
307
- state.isLoading = true
308
- state.errorMessage = nil
281
+ case .onAppear where !state.loading:
282
+ state.loading = true
309
283
  return .run { send in
310
- do {
311
- let trips = try await tripClient.fetchAll()
312
- await send(.tripsLoaded(trips))
313
- } catch {
314
- await send(.tripsFailed(error.localizedDescription))
315
- }
284
+ let sessions = try await workoutClient.fetchAll()
285
+ await send(.loaded(sessions))
316
286
  }
317
- case .tripsLoaded(let trips):
318
- state.trips = IdentifiedArray(uniqueElements: trips)
319
- state.isLoading = false
287
+ case .loaded(let sessions):
288
+ state.loading = false
289
+ state.sessions = IdentifiedArray(uniqueElements: sessions)
320
290
  return .none
321
- case .tripsFailed(let message):
322
- state.errorMessage = message
323
- state.isLoading = false
291
+ case .onAppear:
324
292
  return .none
325
- case .deleteTrip(let id):
326
- state.trips.remove(id: id)
327
- return .run { _ in try await tripClient.delete(id) }
293
+ case .removeTapped(let id):
294
+ state.sessions.remove(id: id)
295
+ return .run { [id] _ in
296
+ try await workoutClient.delete(id)
297
+ }
328
298
  }
329
299
  }
330
300
  }
331
301
  }
332
302
  ```
333
303
 
334
- **Use TCA when:** You need deterministic state transitions for complex state
335
- flows, structured side-effect sequencing, feature composition, strong reducer
336
- testing, or app-wide dependency injection.
304
+ Choose it for complex flows that must be deterministic, effects that need
305
+ sequencing, features composed from smaller features, reducer-level testing, and
306
+ dependency injection across the whole app.
337
307
 
338
308
  ## Clean Architecture
339
309
 
340
- Layers: **Domain** (entities, use cases, repository protocols) → **Data**
341
- (repository implementations, network, persistence) → **Presentation** (views,
342
- view models). Dependencies point inward.
310
+ Three layers with dependencies pointing inward:
311
+
312
+ - **Domain**: the entities, the use cases, and the protocols repositories must
313
+ satisfy. It imports no UI or persistence framework.
314
+ - **Data**: repository implementations, networking, persistence.
315
+ - **Presentation**: views and view models.
343
316
 
344
317
  ```swift
345
- // Domain layer
346
- protocol TripRepository: Sendable {
347
- func fetchAll() async throws -> [Trip]
348
- func save(_ trip: Trip) async throws
349
- func delete(id: UUID) async throws
318
+ // Domain
319
+ protocol WorkoutRepository: Sendable {
320
+ func fetchAll() async throws -> [Workout]
321
+ func save(_ workout: Workout) async throws
322
+ func delete(_ id: Workout.ID) async throws
350
323
  }
351
324
 
352
- struct FetchUpcomingTripsUseCase: Sendable {
353
- private let repository: TripRepository
354
-
355
- init(repository: TripRepository) {
356
- self.repository = repository
357
- }
325
+ struct UpcomingWorkouts: Sendable {
326
+ let repository: any WorkoutRepository
358
327
 
359
- func execute() async throws -> [Trip] {
360
- try await repository.fetchAll()
361
- .filter { $0.startDate > .now }
362
- .sorted { $0.startDate < $1.startDate }
328
+ func callAsFunction(after now: Date = .now) async throws -> [Workout] {
329
+ let everything = try await repository.fetchAll()
330
+ return everything
331
+ .filter { $0.start > now }
332
+ .sorted { $0.start < $1.start }
363
333
  }
364
334
  }
365
335
 
366
- // Data layer
367
- struct RemoteTripRepository: TripRepository {
368
- private let client: APIClient
336
+ // Data
337
+ struct RemoteWorkoutRepository: WorkoutRepository {
338
+ let api: FitnessAPI
369
339
 
370
- func fetchAll() async throws -> [Trip] {
371
- try await client.request(.get, "/trips")
372
- }
373
- // ...
340
+ func fetchAll() async throws -> [Workout] { try await api.get("/workouts") }
341
+ func save(_ workout: Workout) async throws { try await api.post("/workouts", body: workout) }
342
+ func delete(_ id: Workout.ID) async throws { try await api.delete("/workouts/\(id)") }
374
343
  }
375
344
 
376
- // Presentation layer
345
+ // Presentation
377
346
  @MainActor
378
347
  @Observable
379
- final class UpcomingTripsViewModel {
380
- private(set) var trips: [Trip] = []
381
- private let useCase: FetchUpcomingTripsUseCase
348
+ final class PlannerModel {
349
+ private(set) var upcoming: [Workout] = []
350
+ private let upcomingWorkouts: UpcomingWorkouts
382
351
 
383
- init(useCase: FetchUpcomingTripsUseCase) {
384
- self.useCase = useCase
385
- }
352
+ init(upcomingWorkouts: UpcomingWorkouts) { self.upcomingWorkouts = upcomingWorkouts }
386
353
 
387
- func load() async {
388
- trips = (try? await useCase.execute()) ?? []
389
- }
354
+ func load() async { upcoming = (try? await upcomingWorkouts()) ?? [] }
390
355
  }
391
356
  ```
392
357
 
393
- **Use Clean Architecture when:** Strict separation is required (enterprise,
394
- regulated domains), the domain layer must be testable without any framework
395
- dependencies, or multiple presentation targets share the same business logic.
396
-
397
- ## Coordinator Pattern
398
-
399
- Separates navigation logic from views. Especially useful in UIKit or hybrid
400
- apps with complex navigation flows.
401
-
402
- Keep Coordinators `@MainActor`, inject dependencies at coordinator creation,
403
- and pass user-selection callbacks from view models or controllers back to the
404
- coordinator. The coordinator owns push/modal decisions; feature models own
405
- business logic.
406
-
407
- In pure SwiftUI apps, `NavigationStack` with path-based routing often
408
- replaces the Coordinator pattern. Use Coordinators when you need UIKit
409
- integration or shared navigation logic across platforms.
410
-
411
- ## VIPER
358
+ Reach for it when rules or regulation demand hard separation, when domain code
359
+ has to be tested with no framework present, or when an app, a widget and a
360
+ watch app all run one set of business rules.
412
361
 
413
- VIPER splits a feature into **View**, **Interactor**, **Presenter**,
414
- **Entity**, and **Router** roles. Treat it as a maintenance pattern for apps
415
- that already have strict UIKit module boundaries rather than a default for new
416
- SwiftUI work.
362
+ ## Coordinator pattern
417
363
 
418
- **Use VIPER when:** An existing UIKit codebase already organizes screens as
419
- VIPER modules, teams need explicit handoff contracts between presentation,
420
- business logic, and routing, or a migration must preserve module boundaries
421
- while modernizing internals.
364
+ Coordinators take navigation out of views and controllers. They pay off mostly
365
+ in UIKit and hybrid apps.
422
366
 
423
- **Avoid VIPER when:** A new SwiftUI feature can use MV, MVVM, TCA, or Clean
424
- Architecture with fewer files and clearer data flow.
425
-
426
- ## Migration Between Patterns
427
-
428
- ### ObservableObject → `@Observable`
367
+ - A coordinator is `@MainActor` and receives its dependencies when it is
368
+ created.
369
+ - View models and view controllers report user choices through callbacks; the
370
+ coordinator decides whether to push or present.
371
+ - Feature models keep the business logic. The coordinator only routes.
429
372
 
430
373
  ```swift
431
- // Before (iOS 16)
432
- class TripStore: ObservableObject {
433
- @Published var trips: [Trip] = []
434
- }
435
- // View uses @ObservedObject or @StateObject
436
-
437
- // After (iOS 17+)
438
374
  @MainActor
439
- @Observable
440
- final class TripStore {
441
- var trips: [Trip] = []
442
- }
443
- // View uses @State for owned; plain injection or @Bindable only when needed
444
- ```
445
-
446
- Migration routing: keep Coordinators for UIKit or hybrid boundaries; pure
447
- SwiftUI flows usually own `NavigationStack`/path state. Route detailed route
448
- enums, `NavigationSplitView`, sheets, tabs, and deep links to
449
- `swiftui-navigation`, strict-concurrency diagnostics to `swift-concurrency`,
450
- and fixtures or parameterized tests to `swift-testing`. Migrate per feature module, not app-wide by default; keep each module internally consistent while allowing different modules to use different patterns during incremental adoption.
451
-
452
- ### MVVM → MV (simplifying)
375
+ final class OnboardingCoordinator {
376
+ private let navigation: UINavigationController
377
+ private let account: AccountService
453
378
 
454
- If a view model only passes through model data without transforming it,
455
- remove the view model and let the view observe the model directly.
456
-
457
- ### MV → MVVM (scaling up)
458
-
459
- Extract business logic and data transformation into a view model when:
460
- - The view's `body` contains conditional logic for data formatting
461
- - Multiple views need different projections of the same model
462
- - You need to test logic without instantiating views
379
+ init(navigation: UINavigationController, account: AccountService) {
380
+ self.navigation = navigation
381
+ self.account = account
382
+ }
463
383
 
464
- ### Any → TCA
384
+ func start() {
385
+ let welcome = WelcomeViewController(onContinue: { [weak self] in self?.showSignUp() })
386
+ navigation.setViewControllers([welcome], animated: false)
387
+ }
465
388
 
466
- TCA adoption is typically incremental: wrap one feature's state and actions
467
- in a `Reducer`, migrate its dependencies to `@Dependency`, and test.
389
+ private func showSignUp() {
390
+ let form = SignUpViewController(account: account, onDone: { [weak self] in self?.finish() })
391
+ navigation.pushViewController(form, animated: true)
392
+ }
468
393
 
469
- ## Common Mistakes
394
+ private func finish() { navigation.dismiss(animated: true) }
395
+ }
396
+ ```
470
397
 
471
- | Mistake | Fix |
472
- |---------|-----|
473
- | Using `ObservableObject` in new iOS 17+ code | Use `@Observable`; isolate UI-observed app state to `@MainActor` for Swift 6 data-race safety |
474
- | View model that only forwards model properties | Remove the view model; use MV pattern |
475
- | Massive view model with navigation, networking, and formatting | Split into focused collaborators (coordinator, service, formatter) |
476
- | Choosing TCA for a two-screen app | Start with MV; adopt TCA when composition and testing demands justify it |
477
- | Protocol-heavy Clean Architecture for a simple feature | Match architecture complexity to feature complexity |
478
- | Coordinator pattern in pure SwiftUI without UIKit needs | Use `NavigationStack` path-based routing instead |
479
- | Starting new SwiftUI modules with VIPER | Reserve VIPER for legacy UIKit maintenance or strict module-boundary migrations |
480
- | Mixing architecture patterns inside one feature module | Keep one pattern inside each feature module; migrate different modules independently when needed |
398
+ In a pure SwiftUI app, a `NavigationStack` driven by a path usually does this
399
+ job without a coordinator. Keep coordinators for UIKit integration or for
400
+ navigation logic shared across platforms.
481
401
 
482
- ## Review Checklist
402
+ ## VIPER
483
403
 
484
- - [ ] Architecture choice is justified by feature complexity and team needs
485
- - [ ] Architecture identifies the model/store owner; `@State`, plain injection, and `@Bindable` wiring hand off to `swiftui-patterns`
486
- - [ ] Dependencies are injected, not created internally (testability)
487
- - [ ] SwiftUI MV mechanics, `NavigationSplitView`, strict-concurrency diagnostics, fixtures, and parameterized tests hand off to sibling skills explicitly
488
- - [ ] State mutations happen in a clear, auditable location
489
- - [ ] View models (if present) are testable without views
490
- - [ ] No god objects - responsibilities are distributed appropriately
491
- - [ ] Pattern is consistent within each feature module, including during migrations
404
+ View, Interactor, Presenter, Entity, Router. Its value today is keeping
405
+ existing UIKit modules with hard boundaries maintainable; it is not where new
406
+ SwiftUI work should begin.
407
+
408
+ Use it when an existing UIKit codebase is already organized into VIPER modules,
409
+ when teams depend on its explicit handoff contracts, or when a migration has to
410
+ keep those module boundaries intact. For new SwiftUI features, pick one of the
411
+ lighter patterns above; they need far fewer files for the same result.
412
+
413
+ ## Migration between patterns
414
+
415
+ ### ObservableObject to @Observable
416
+
417
+ | Before (iOS 16) | After (iOS 17+) |
418
+ | --- | --- |
419
+ | `class Model: ObservableObject` with `@Published` properties | `@MainActor @Observable final class Model` with plain stored properties |
420
+ | `@StateObject var model` | `@State private var model` for a model the view owns |
421
+ | `@ObservedObject var model` | A plain `let` or `var` property for an injected model the view only reads |
422
+ | `$model.property` bindings through `@ObservedObject` | `@Bindable var model` only where a binding is needed |
423
+
424
+ The wiring details belong to `swiftui-patterns`.
425
+
426
+ ### Routing during a migration
427
+
428
+ - Keep coordinators at UIKit and hybrid boundaries; let pure SwiftUI areas own
429
+ a path-driven `NavigationStack`.
430
+ - Anything about route enums, split views, sheet or tab presentation, or deep
431
+ link handling goes to `swiftui-navigation`.
432
+ - Isolation and `Sendable` errors that appear during the move go to
433
+ `swift-concurrency`.
434
+ - Test fixtures and parameterized tests go to `swift-testing`.
435
+
436
+ ### Strategy
437
+
438
+ Migrate one feature module at a time, not the whole app at once. Each module
439
+ stays internally consistent; different modules may use different patterns
440
+ while adoption is in progress.
441
+
442
+ - **MVVM to MV**: delete view models that only forward model properties.
443
+ - **MV to MVVM**: when `body` fills up with formatting conditionals, when two
444
+ screens want the same data shaped differently, or when logic must be tested
445
+ without a view.
446
+ - **Any pattern to TCA**: go incrementally. Pick one feature, express its state
447
+ and actions as a `Reducer`, move what it depends on behind `@Dependency`,
448
+ cover it with tests, then take the next feature.
449
+
450
+ ## Common mistakes
451
+
452
+ | Symptom | Fix |
453
+ | --- | --- |
454
+ | New iOS 17+ code still built on `ObservableObject` | Switch to `@Observable` and keep state the UI reads on `@MainActor`, which Swift 6 needs to rule out data races |
455
+ | A view model whose properties just mirror the model | Delete the layer; MV is enough |
456
+ | One oversized view model that routes, fetches and formats | Give routing, fetching and formatting to separate collaborators |
457
+ | TCA adopted for an app with two screens | Begin with MV and escalate later |
458
+ | Protocols and layers everywhere for a small feature | Size the structure to the problem |
459
+ | Coordinators in an all-SwiftUI app with no UIKit | Let a path-driven `NavigationStack` route |
460
+ | VIPER chosen for a brand-new SwiftUI module | Leave VIPER to the legacy code |
461
+ | Two or three patterns mixed in one feature module | Settle on one per module |
462
+
463
+ ## Review checklist
464
+
465
+ - [ ] The pattern is justified by the feature's complexity and the team's needs
466
+ - [ ] Each model or store has a named owner; the `@State`, injection and `@Bindable` details are left to `swiftui-patterns`
467
+ - [ ] Models receive their dependencies; they never construct them
468
+ - [ ] Handoffs are explicit: MV wiring, split view layout, concurrency errors, fixtures and parameterized tests each point at their skill
469
+ - [ ] State changes happen in one clear, auditable place
470
+ - [ ] View models and stores can be exercised in tests with no view involved
471
+ - [ ] No single object has taken over the feature
472
+ - [ ] Each feature module uses one pattern consistently, including mid-migration
492
473
 
493
474
  ## References
494
475
 
495
- - Apple docs: [Observation](https://sosumi.ai/documentation/observation) | [Observable](https://sosumi.ai/documentation/observation/observable())
496
- - Apple docs: [Migrating from ObservableObject to Observable](https://sosumi.ai/documentation/swiftui/migrating-from-the-observable-object-protocol-to-the-observable-macro)
497
- - Apple docs: [`State`](https://sosumi.ai/documentation/swiftui/state) | [`Bindable`](https://sosumi.ai/documentation/swiftui/bindable) | [`Environment`](https://sosumi.ai/documentation/swiftui/environment)
498
- - Apple docs: [`NavigationStack`](https://sosumi.ai/documentation/swiftui/navigationstack)
499
- - Apple docs: [Swift Testing](https://sosumi.ai/documentation/testing)
500
- - TCA docs: [ComposableArchitecture](https://sosumi.ai/external/https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture)
476
+ - [Observation](https://developer.apple.com/documentation/observation)
477
+ - [Observable()](https://developer.apple.com/documentation/observation/observable())
478
+ - [Migrating from the Observable Object protocol to the Observable macro](https://developer.apple.com/documentation/swiftui/migrating-from-the-observable-object-protocol-to-the-observable-macro)
479
+ - [State](https://developer.apple.com/documentation/swiftui/state)
480
+ - [Bindable](https://developer.apple.com/documentation/swiftui/bindable)
481
+ - [Environment](https://developer.apple.com/documentation/swiftui/environment)
482
+ - [NavigationStack](https://developer.apple.com/documentation/swiftui/navigationstack)
483
+ - [Swift Testing](https://developer.apple.com/documentation/testing)
484
+ - [The Composable Architecture documentation](https://swiftpackageindex.com/pointfreeco/swift-composable-architecture/main/documentation/composablearchitecture)