@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,145 +1,144 @@
1
1
  # Architecture Patterns
2
2
 
3
- ## Contents
4
- - [MV Patterns](#mv-patterns)
5
- - [App Wiring and Dependency Graph](#app-wiring-and-dependency-graph)
6
- - [Lightweight Clients](#lightweight-clients)
7
-
8
- ## MV Patterns
9
-
10
- Default to Model-View (MV) in SwiftUI. Views are lightweight state expressions; models and services own business logic. Do not introduce view models unless the existing code already requires them.
11
-
12
- ### Contents
13
-
14
- - [Core Principles](#core-principles)
15
- - [Why Not MVVM](#why-not-mvvm)
16
- - [MV Pattern in Practice](#mv-pattern-in-practice)
17
- - [When a ViewModel Already Exists](#when-a-viewmodel-already-exists)
18
- - [When a New ViewModel Is Justified](#when-a-new-viewmodel-is-justified)
19
- - [Environment vs. Initializer Injection](#environment-vs-initializer-injection)
20
- - [Testing Strategy](#testing-strategy)
21
- - [Source](#source)
22
-
23
- ### Core Principles
24
-
25
- - Views orchestrate UI flow using `@State`, `@Environment`, `@Query`, `.task`, and `.onChange`
26
- - Services and shared models live in the environment, are testable in isolation, and encapsulate complexity
27
- - Split large views into smaller subviews rather than introducing a view model
28
- - Test models, services, and business logic; views should stay simple and declarative
29
-
30
- ### Why Not MVVM
3
+ The reasoning behind the Model-View default, the cases where a view model still
4
+ earns its place, and a reference wiring for the app shell.
31
5
 
32
- SwiftUI views are structs -- lightweight, disposable, and recreated frequently. Adding a ViewModel means fighting the framework's core design. Apple's own WWDC sessions (*Data Flow Through SwiftUI*, *Data Essentials in SwiftUI*, *Discover Observation in SwiftUI*) barely mention ViewModels.
33
-
34
- Every ViewModel adds:
35
- - More complexity and objects to synchronize
36
- - More indirection and cognitive overhead
37
- - Manual data fetching that duplicates SwiftUI/SwiftData mechanisms
38
-
39
- ### MV Pattern in Practice
6
+ ## Contents
40
7
 
41
- #### View with Environment-Injected Service
8
+ 1. [MV Principles](#1-mv-principles)
9
+ 2. [Why Not MVVM](#2-why-not-mvvm)
10
+ 3. [MV in Practice](#3-mv-in-practice)
11
+ 4. [Working With an Existing View Model](#4-working-with-an-existing-view-model)
12
+ 5. [When a New View Model Is Justified](#5-when-a-new-view-model-is-justified)
13
+ 6. [Environment or Initializer Injection](#6-environment-or-initializer-injection)
14
+ 7. [Testing Strategy](#7-testing-strategy)
15
+ 8. [App Shell and Dependency Graph](#8-app-shell-and-dependency-graph)
16
+ 9. [Lightweight Clients](#9-lightweight-clients)
17
+ 10. [Further Reading](#10-further-reading)
18
+
19
+ ## 1. MV Principles
20
+
21
+ - Views run the UI flow with `@State`, `@Environment`, `@Query`, `.task` and
22
+ `.onChange`.
23
+ - Services and shared models live in the environment. Each one can be tested
24
+ on its own and keeps its complexity behind a small surface.
25
+ - When a view gets big, extract subviews first. Add a view model only when the
26
+ existing code already depends on one.
27
+
28
+ ## 2. Why Not MVVM
29
+
30
+ - A SwiftUI view is a struct that is cheap to build and rebuilt often. A view
31
+ model that mirrors it pushes against that design.
32
+ - Apple's data-flow sessions barely mention view models: *Data Flow Through
33
+ SwiftUI* (WWDC19), *Data Essentials in SwiftUI* (WWDC20), *Discover
34
+ Observation in SwiftUI* (WWDC23).
35
+ - Every view model adds cost: another object to keep in step with the view,
36
+ another hop to read through, and hand-written fetching that repeats what
37
+ SwiftUI and SwiftData already do.
38
+
39
+ ## 3. MV in Practice
40
+
41
+ ### A view backed by an environment service
42
42
 
43
43
  ```swift
44
- struct FeedView: View {
45
- @Environment(FeedClient.self) private var client
46
- @Environment(AppTheme.self) private var theme
47
-
48
- enum ViewState {
49
- case loading, error(String), loaded([Post])
44
+ struct ArticlesView: View {
45
+ @Environment(NewsService.self) private var news
46
+ @Environment(Branding.self) private var branding
47
+
48
+ enum Phase {
49
+ case fetching
50
+ case failed(String)
51
+ case ready([Article])
50
52
  }
51
53
 
52
- @State private var viewState: ViewState = .loading
53
- @State private var isRefreshing = false
54
+ @State private var phase: Phase = .fetching
54
55
 
55
56
  var body: some View {
56
57
  NavigationStack {
57
58
  List {
58
- switch viewState {
59
- case .loading:
60
- ProgressView("Loading feed...")
59
+ switch phase {
60
+ case .fetching:
61
+ ProgressView("Fetching articles")
61
62
  .frame(maxWidth: .infinity)
62
63
  .listRowSeparator(.hidden)
63
- case .error(let message):
64
- ContentUnavailableView("Error", systemImage: "exclamationmark.triangle",
65
- description: Text(message))
66
- .listRowSeparator(.hidden)
67
- case .loaded(let posts):
68
- ForEach(posts) { post in
69
- PostRowView(post: post)
70
- }
64
+ case .failed(let reason):
65
+ ContentUnavailableView("No articles",
66
+ systemImage: "wifi.exclamationmark",
67
+ description: Text(reason))
68
+ .listRowSeparator(.hidden)
69
+ case .ready(let articles):
70
+ ForEach(articles) { ArticleRow(article: $0) }
71
71
  }
72
72
  }
73
73
  .listStyle(.plain)
74
- .refreshable { await loadFeed() }
75
- .task { await loadFeed() }
74
+ .refreshable { await fetch() }
75
+ .task { await fetch() }
76
76
  }
77
77
  }
78
78
 
79
- private func loadFeed() async {
80
- do {
81
- let posts = try await client.getFeed()
82
- viewState = .loaded(posts)
83
- } catch {
84
- viewState = .error(error.localizedDescription)
85
- }
79
+ private func fetch() async {
80
+ do { phase = .ready(try await news.latest()) }
81
+ catch { phase = .failed(error.localizedDescription) }
86
82
  }
87
83
  }
88
84
  ```
89
85
 
90
- #### Using .task(id:) and .onChange
86
+ ### Modifiers as small reducers
91
87
 
92
- SwiftUI modifiers act as small state reducers:
88
+ Treat `.task(id:)` and `.onChange` as tiny reducers: an input changes, a bit of
89
+ state follows.
93
90
 
94
91
  ```swift
95
- .task(id: searchText) {
96
- guard !searchText.isEmpty else { return }
97
- await searchFeed(query: searchText)
92
+ .task(id: filterText) {
93
+ guard !filterText.isEmpty else { return }
94
+ await runFilter(filterText)
98
95
  }
99
- .onChange(of: isInSearch, initial: false) {
100
- guard !isInSearch else { return }
101
- Task { await fetchSuggestedFeed() }
96
+ .onChange(of: isFiltering, initial: false) { _, active in
97
+ if !active {
98
+ Task { await loadDefaultArticles() }
99
+ }
102
100
  }
103
101
  ```
104
102
 
105
- #### App-Level Environment Setup
103
+ ### Dependencies installed once at the app level
106
104
 
107
105
  ```swift
108
106
  @main
109
- struct MyApp: App {
110
- @State var client = APIClient()
111
- @State var auth = Auth()
112
- @State var router = AppRouter(initialTab: .feed)
107
+ struct NewsReaderApp: App {
108
+ @State private var news = NewsService()
109
+ @State private var session = SessionManager()
110
+ @State private var router = TabRouter(startTab: .today)
113
111
 
114
112
  var body: some Scene {
115
113
  WindowGroup {
116
- ContentView()
117
- .environment(client)
118
- .environment(auth)
114
+ RootView()
115
+ .environment(news)
116
+ .environment(session)
119
117
  .environment(router)
120
118
  }
121
119
  }
122
120
  }
123
121
  ```
124
122
 
125
- All dependencies are injected once and available everywhere.
123
+ Each dependency is created one time and every view below can reach it.
126
124
 
127
- #### SwiftData: The Perfect MV Example
125
+ ### SwiftData directly in views
128
126
 
129
- SwiftData was built to work directly in views:
127
+ SwiftData is designed to be queried from views. Wrapping it in a view model
128
+ means fetching and refreshing by hand, plus boilerplate.
130
129
 
131
130
  ```swift
132
- struct BookListView: View {
133
- @Query private var books: [Book]
134
- @Environment(\.modelContext) private var modelContext
131
+ struct ShelfView: View {
132
+ @Query(sort: \Novel.title) private var novels: [Novel]
133
+ @Environment(\.modelContext) private var context
135
134
 
136
135
  var body: some View {
137
136
  List {
138
- ForEach(books) { book in
139
- BookRowView(book: book)
137
+ ForEach(novels) { novel in
138
+ NovelRow(novel: novel)
140
139
  .swipeActions {
141
140
  Button("Delete", role: .destructive) {
142
- modelContext.delete(book)
141
+ context.delete(novel)
143
142
  }
144
143
  }
145
144
  }
@@ -148,150 +147,133 @@ struct BookListView: View {
148
147
  }
149
148
  ```
150
149
 
151
- Forcing a ViewModel here means manual fetching, manual refresh, and boilerplate everywhere.
152
-
153
- ### When a ViewModel Already Exists
154
-
155
- If a ViewModel exists in the codebase:
156
- - Make it non-optional when possible
157
- - Pass dependencies via `init`, then forward them into the ViewModel in the view's `init`
158
- - Store as `@State` in the root view that owns it
159
- - Avoid `bootstrapIfNeeded` patterns
150
+ ## 4. Working With an Existing View Model
160
151
 
161
- ```swift
162
- @State private var viewModel: SomeViewModel
163
-
164
- init(dependency: Dependency) {
165
- _viewModel = State(initialValue: SomeViewModel(dependency: dependency))
166
- }
167
- ```
152
+ When the project already has view models, keep them tidy:
168
153
 
169
- Modern `@Observable` ViewModel with child-view binding:
154
+ - Make the view model non-optional where you can.
155
+ - Take dependencies in the view's `init` and hand them to the view model
156
+ there.
157
+ - Hold the view model in `@State` in the view that owns it.
158
+ - Skip lazy `bootstrapIfNeeded`-style setup.
170
159
 
171
160
  ```swift
172
- @MainActor @Observable final class ProfileViewModel {
173
- var name: String = ""
174
- var isSaving: Bool = false
175
-
176
- private let client: ProfileClient
177
-
178
- init(client: ProfileClient) {
179
- self.client = client
180
- }
181
-
182
- func save() async throws {
183
- isSaving = true
184
- defer { isSaving = false }
185
- try await client.update(name: name)
161
+ @MainActor @Observable
162
+ final class AccountEditorModel {
163
+ var displayName = ""
164
+ var isSubmitting = false
165
+ private let api: AccountAPI
166
+
167
+ init(api: AccountAPI) { self.api = api }
168
+
169
+ func submit() async throws {
170
+ isSubmitting = true
171
+ defer { isSubmitting = false }
172
+ try await api.rename(to: displayName)
186
173
  }
187
174
  }
188
175
 
189
- // Owner view creates via @State
190
- struct ProfileScreen: View {
191
- @State private var viewModel: ProfileViewModel
176
+ struct AccountEditorScreen: View {
177
+ @State private var model: AccountEditorModel
192
178
 
193
- init(client: ProfileClient) {
194
- _viewModel = State(initialValue: ProfileViewModel(client: client))
179
+ init(api: AccountAPI) {
180
+ _model = State(initialValue: AccountEditorModel(api: api))
195
181
  }
196
182
 
197
- var body: some View {
198
- ProfileForm(viewModel: viewModel)
199
- }
183
+ var body: some View { AccountEditorForm(model: model) }
200
184
  }
201
185
 
202
- // Child view receives and binds
203
- struct ProfileForm: View {
204
- @Bindable var viewModel: ProfileViewModel
186
+ struct AccountEditorForm: View {
187
+ @Bindable var model: AccountEditorModel
205
188
 
206
189
  var body: some View {
207
- TextField("Name", text: $viewModel.name)
208
- Button("Save") { Task { try? await viewModel.save() } }
209
- .disabled(viewModel.isSaving)
190
+ Form {
191
+ TextField("Display name", text: $model.displayName)
192
+ Button("Submit") {
193
+ Task { try? await model.submit() }
194
+ }
195
+ .disabled(model.isSubmitting)
196
+ }
210
197
  }
211
198
  }
212
199
  ```
213
200
 
214
- ### When a New ViewModel Is Justified
215
-
216
- The MV pattern is the default. Introduce a ViewModel only when the view would be hard to read or test without one:
217
-
218
- - **Multi-step workflows** - onboarding, checkout, or wizard flows where each step mutates shared draft state
219
- - **Non-trivial business logic** - validation chains, derived state from multiple sources, or transformation pipelines that don't belong in a lightweight client
220
- - **Coordinated async streams** - the view orchestrates multiple publishers or `AsyncSequence` values with interdependent state transitions
221
- - **Existing test surface** - the codebase already tests against a ViewModel interface and rewriting to MV would be high cost, low reward
222
-
223
- The bar is "this view would be hard to read and test without a ViewModel," not "I'm used to MVVM."
201
+ ## 5. When a New View Model Is Justified
224
202
 
225
- ### Environment vs. Initializer Injection
203
+ Add one only when the view would otherwise be hard to read or hard to test.
204
+ Cases that qualify:
226
205
 
227
- **Use `@Environment` when** the dependency is shared across many views at different depths. Threading it through every intermediate initializer adds noise:
228
- - App-wide services: auth, network client, theme, router
229
- - SwiftData `ModelContext`
230
- - Feature-scoped stores injected at a navigation root
206
+ - Multi-step flows such as onboarding, checkout or a wizard, where every step
207
+ edits the same draft.
208
+ - Real business logic: validation chains, state derived from several sources,
209
+ transformation pipelines that do not fit a lightweight client.
210
+ - Coordinating several publishers or `AsyncSequence` streams whose transitions
211
+ depend on each other.
212
+ - A code base that already tests against a view model interface, where moving
213
+ to MV would cost more than it returns.
231
214
 
232
- **Use initializer parameters when** the data is specific to this view instance. Makes the view's requirements explicit and keeps previews simple:
233
- - The selected item, filter mode, or configuration
234
- - Parent-to-child data that only one view needs
235
- - Values known at call site that don't change
215
+ The test is readability and testability. Familiarity with MVVM from other
216
+ platforms is not a reason.
236
217
 
237
- Rule of thumb: if three or more intermediate views would need to accept and forward a parameter just to reach a deeply nested consumer, move it to the environment.
218
+ ## 6. Environment or Initializer Injection
238
219
 
239
- ### Testing Strategy
220
+ Use `@Environment` for dependencies many views need at different depths:
240
221
 
241
- - Unit test services and business logic
242
- - Test models and transformations
243
- - Use SwiftUI previews for visual regression
244
- - Use UI automation for end-to-end tests
245
- - Views should be simple enough that they do not need dedicated unit tests
222
+ - app-wide services: authentication, the network client, theme, router
223
+ - the SwiftData `ModelContext`
224
+ - a feature store installed at a navigation root
246
225
 
247
- ### Source
226
+ Use initializer parameters for data that belongs to one instance:
248
227
 
249
- Based on guidance from "SwiftUI in 2025: Forget MVVM" (Thomas Ricouard) and Apple WWDC sessions on SwiftUI data flow.
228
+ - the selected item, a filter mode, a configuration value
229
+ - data one child needs from its parent
230
+ - values fixed at the call site
250
231
 
251
- ## App Wiring and Dependency Graph
232
+ Init parameters make requirements visible and keep previews simple. If three or
233
+ more views in between would only forward a parameter, move it to the
234
+ environment.
252
235
 
253
- ### Contents
236
+ ## 7. Testing Strategy
254
237
 
255
- - [Intent](#intent)
256
- - [Recommended Structure](#recommended-structure)
257
- - [Root Shell Example](#root-shell-example)
258
- - [Dependency Graph Modifier](#dependency-graph-modifier)
259
- - [SwiftData / ModelContainer](#swiftdata-modelcontainer)
260
- - [Sheet Routing (Enum-Driven)](#sheet-routing-enum-driven)
261
- - [App Entry Point](#app-entry-point)
262
- - [Deep Linking](#deep-linking)
263
- - [When to Use](#when-to-use)
264
- - [Caveats](#caveats)
238
+ - Unit test services and business rules.
239
+ - Test models and data transformations.
240
+ - Use SwiftUI previews to catch visual regressions.
241
+ - Cover end-to-end flows with UI automation.
242
+ - Keep views simple enough that they need no unit tests of their own.
265
243
 
266
- ### Intent
244
+ ## 8. App Shell and Dependency Graph
267
245
 
268
- Wire the app shell (TabView + NavigationStack + sheets) and install a global dependency graph (environment objects, services, streaming clients, SwiftData ModelContainer) in one place.
246
+ ### Goal
269
247
 
270
- ### Recommended Structure
248
+ Wire the shell (tab view, navigation stacks, sheets) and install global
249
+ dependencies (environment objects, services, streaming clients, the SwiftData
250
+ `ModelContainer`) in one place:
271
251
 
272
- 1. Root view sets up tabs, per-tab routers, and sheets.
273
- 2. A dedicated view modifier installs global dependencies and lifecycle tasks (auth state, streaming watchers, push tokens, data containers).
274
- 3. Feature views pull only what they need from the environment; feature-specific state stays local.
252
+ 1. The root view builds the tabs, one router per tab, and the sheets.
253
+ 2. One view modifier installs global dependencies and lifecycle work: auth
254
+ state, streaming watchers, push tokens, data containers.
255
+ 3. Feature views read only what they need from the environment. Feature state
256
+ stays inside the feature.
275
257
 
276
- ### Root Shell Example
258
+ ### Root shell
277
259
 
278
260
  ```swift
279
261
  @MainActor
280
- struct AppView: View {
281
- @State private var selectedTab: AppTab = .home
282
- @State private var tabRouter = TabRouter()
262
+ struct RootView: View {
263
+ @State private var currentTab: MainTab = .today
264
+ @State private var routers = TabRouter()
283
265
 
284
266
  var body: some View {
285
- TabView(selection: $selectedTab) {
286
- ForEach(AppTab.allCases) { tab in
287
- let router = tabRouter.router(for: tab)
267
+ TabView(selection: $currentTab) {
268
+ ForEach(MainTab.allCases) { tab in
269
+ let router = routers.router(for: tab)
288
270
  Tab(value: tab) {
289
- NavigationStack(path: tabRouter.binding(for: tab)) {
290
- tab.makeContentView()
271
+ NavigationStack(path: routers.pathBinding(for: tab)) {
272
+ tab.rootView()
291
273
  }
292
- .withSheetDestinations(sheet: Binding(
293
- get: { router.presentedSheet },
294
- set: { router.presentedSheet = $0 }
274
+ .presentsSheets(Binding(
275
+ get: { router.activeSheet },
276
+ set: { router.activeSheet = $0 }
295
277
  ))
296
278
  .environment(router)
297
279
  } label: {
@@ -300,259 +282,286 @@ struct AppView: View {
300
282
  }
301
283
  }
302
284
  .tabBarMinimizeBehavior(.onScrollDown)
303
- .withAppDependencyGraph()
285
+ .installAppServices()
304
286
  }
305
287
  }
306
288
  ```
307
289
 
308
- #### AppTab Enum
290
+ `Tab(value:)` needs iOS 18; `.tabBarMinimizeBehavior(_:)` needs iOS 26.
309
291
 
310
292
  ```swift
311
293
  @MainActor
312
- enum AppTab: Identifiable, Hashable, CaseIterable {
313
- case home, notifications, settings
314
- var id: String { String(describing: self) }
294
+ enum MainTab: Identifiable, Hashable, CaseIterable {
295
+ case today, alerts, preferences
296
+
297
+ nonisolated var id: String { String(describing: self) }
315
298
 
316
299
  @ViewBuilder
317
- func makeContentView() -> some View {
300
+ func rootView() -> some View {
318
301
  switch self {
319
- case .home: HomeView()
320
- case .notifications: NotificationsView()
321
- case .settings: SettingsView()
302
+ case .today: TodayView()
303
+ case .alerts: AlertsView()
304
+ case .preferences: PreferencesView()
322
305
  }
323
306
  }
324
307
 
325
308
  @ViewBuilder
326
309
  var label: some View {
327
310
  switch self {
328
- case .home: Label("Home", systemImage: "house")
329
- case .notifications: Label("Notifications", systemImage: "bell")
330
- case .settings: Label("Settings", systemImage: "gear")
311
+ case .today: Label("Today", systemImage: "house")
312
+ case .alerts: Label("Alerts", systemImage: "bell")
313
+ case .preferences: Label("Preferences", systemImage: "gear")
331
314
  }
332
315
  }
333
316
  }
334
- ```
335
317
 
336
- #### Router Skeleton
337
-
338
- ```swift
339
- @MainActor
340
- @Observable
341
- final class RouterPath {
342
- var path: [Route] = []
343
- var presentedSheet: SheetDestination?
318
+ @MainActor @Observable
319
+ final class NavigationRouter {
320
+ var path: [Destination] = []
321
+ var activeSheet: SheetRoute?
344
322
  }
345
323
 
346
- enum Route: Hashable {
347
- case detail(id: String)
324
+ enum Destination: Hashable {
325
+ case article(id: String)
348
326
  }
349
327
  ```
350
328
 
351
- ### Dependency Graph Modifier
329
+ `TabRouter` is a small `@Observable` holder that keeps one `NavigationRouter`
330
+ per tab and vends a `Binding` to each router's `path`.
331
+
332
+ `MainTab` is `@MainActor` because it builds views, so its `id` is marked
333
+ `nonisolated`; otherwise the `Identifiable` conformance would cross into
334
+ main-actor code and Swift 6 rejects it.
352
335
 
353
- Use a single modifier to install environment objects and handle lifecycle hooks. This keeps wiring consistent and avoids forgetting a dependency at call sites.
336
+ ### Dependency graph modifier
337
+
338
+ Install every environment object and lifecycle hook through one modifier. Call
339
+ sites stay consistent and none of them can forget a dependency.
354
340
 
355
341
  ```swift
356
342
  extension View {
357
- func withAppDependencyGraph(
358
- client: APIClient = .shared,
359
- auth: Auth = .shared,
360
- theme: Theme = .shared,
361
- toastCenter: ToastCenter = .shared
343
+ func installAppServices(
344
+ api: APIClient = .shared,
345
+ session: SessionManager = .shared,
346
+ branding: Branding = .shared,
347
+ banners: BannerCenter = .shared
362
348
  ) -> some View {
363
- environment(client)
364
- .environment(auth)
365
- .environment(theme)
366
- .environment(toastCenter)
367
- .task(id: auth.currentAccount?.id) {
368
- // Re-seed services when account changes
369
- await client.configure(for: auth.currentAccount)
349
+ environment(api)
350
+ .environment(session)
351
+ .environment(branding)
352
+ .environment(banners)
353
+ .task(id: session.activeUser?.id) {
354
+ await api.prepare(for: session.activeUser)
370
355
  }
371
356
  }
372
357
  }
373
358
  ```
374
359
 
375
- Notes:
376
- - The `.task(id:)` hooks respond to account/client changes, re-seeding services and watcher state.
377
- - Keep the modifier focused on global wiring; feature-specific state stays within features.
378
- - Adjust types to match your project.
360
+ The `.task(id:)` hook runs again when the signed-in account changes, so
361
+ services and watchers are re-seeded for the new user. Keep this modifier to
362
+ global wiring; feature state belongs to features. Rename the types to match the
363
+ project.
379
364
 
380
- ### SwiftData / ModelContainer
365
+ ### ModelContainer
381
366
 
382
- Install `ModelContainer` at the root so all feature views share the same store:
367
+ Install the `ModelContainer` at the root so every feature shares one store:
383
368
 
384
369
  ```swift
385
370
  extension View {
386
- func withModelContainer() -> some View {
387
- modelContainer(for: [Draft.self, LocalTimeline.self, TagGroup.self])
371
+ func installDataStore() -> some View {
372
+ modelContainer(for: [Note.self, Folder.self, Attachment.self])
388
373
  }
389
374
  }
390
375
  ```
391
376
 
392
- A single container avoids duplicated stores per sheet or tab and keeps data consistent.
377
+ A single container avoids a separate store per sheet or tab and keeps data
378
+ consistent.
393
379
 
394
- ### Sheet Routing (Enum-Driven)
380
+ ### Enum-driven sheets
395
381
 
396
- Centralize sheets with a small enum and a helper modifier:
382
+ Keep every sheet in one small `Identifiable` enum plus a helper modifier:
397
383
 
398
384
  ```swift
399
- enum SheetDestination: Identifiable {
400
- case composer
401
- case settings
402
- var id: String { String(describing: self) }
385
+ enum SheetRoute: Identifiable {
386
+ case newPost
387
+ case accountSettings
388
+
389
+ var id: Self { self }
403
390
  }
404
391
 
405
392
  extension View {
406
- func withSheetDestinations(sheet: Binding<SheetDestination?>) -> some View {
407
- sheet(item: sheet) { destination in
408
- switch destination {
409
- case .composer:
410
- ComposerView().withEnvironments()
411
- case .settings:
412
- SettingsView().withEnvironments()
393
+ func presentsSheets(_ route: Binding<SheetRoute?>) -> some View {
394
+ sheet(item: route) { route in
395
+ switch route {
396
+ case .newPost: NewPostView().installAppServices()
397
+ case .accountSettings: AccountSettingsView().installAppServices()
413
398
  }
414
399
  }
415
400
  }
416
401
  }
417
402
  ```
418
403
 
419
- Enum-driven sheets keep presentation centralized and testable; adding a new sheet means one enum case and one switch branch.
404
+ A new sheet costs one case and one branch. Presentation stays in one place and
405
+ is easy to test. Sheets start a new presentation context, so each one applies
406
+ the dependency modifier again.
420
407
 
421
- ### App Entry Point
408
+ ### App entry point
422
409
 
423
410
  ```swift
424
411
  @main
425
- struct MyApp: App {
426
- @State var client = APIClient()
427
- @State var auth = Auth()
428
- @State var router = AppRouter(initialTab: .home)
412
+ struct FieldNotesApp: App {
413
+ @State private var api = APIClient.shared
414
+ @State private var session = SessionManager.shared
429
415
 
430
416
  var body: some Scene {
431
417
  WindowGroup {
432
- AppView()
433
- .environment(client)
434
- .environment(auth)
435
- .environment(router)
418
+ RootView()
419
+ .environment(api)
420
+ .environment(session)
421
+ .installDataStore()
436
422
  }
437
423
  }
438
424
  }
439
425
  ```
440
426
 
441
- ### Deep Linking
427
+ ### Deep links
442
428
 
443
- Store `NavigationPath` as `Codable` for state restoration. Handle incoming URLs with `.onOpenURL`:
429
+ - For state restoration, save the navigation path. `NavigationPath.codable`
430
+ gives a `Codable` representation when every element is `Codable`.
431
+ - Handle incoming URLs with `.onOpenURL`:
444
432
 
445
433
  ```swift
446
434
  .onOpenURL { url in
447
- guard let route = Route(from: url) else { return }
448
- router.navigate(to: route)
435
+ guard let destination = Destination(url: url) else { return }
436
+ router.path.append(destination)
449
437
  }
450
438
  ```
451
439
 
452
- See the `swiftui-navigation` skill for full URL routing patterns.
440
+ Complete URL routing is covered in `swiftui-navigation`.
453
441
 
454
- ### When to Use
442
+ ### When this structure fits
455
443
 
456
- - Apps with multiple packages/modules that share environment objects and services
457
- - Apps that need to react to account/client changes and rewire streaming/push safely
458
- - Any app that wants consistent TabView + NavigationStack + sheet wiring without repeating environment setup
444
+ - Several packages or modules share the same environment objects and services.
445
+ - The app has to react to account or client changes and rewire streaming or
446
+ push safely.
447
+ - You want tabs, navigation stacks and sheets wired the same way everywhere
448
+ without repeating environment setup.
459
449
 
460
450
  ### Caveats
461
451
 
462
- - Keep the dependency modifier slim; do not put feature state or heavy logic there
463
- - Ensure `.task(id:)` work is lightweight or cancelled appropriately; long-running work belongs in services
464
- - If unauthenticated clients exist, gate streaming/watch calls to avoid reconnect spam
465
-
466
- ## Lightweight Clients
452
+ - Keep the dependency modifier slim: no feature state, no heavy logic.
453
+ - Work in `.task(id:)` must be short or cancel cleanly. Long-running work
454
+ belongs in services.
455
+ - When a client can exist without a signed-in user, gate streaming and watch
456
+ calls so the app does not fall into a reconnect loop.
467
457
 
468
- Use this pattern to keep networking or service dependencies simple and testable without introducing a full view model or heavy DI framework. It works well for SwiftUI apps where you want a small, composable API surface that can be swapped in previews/tests.
458
+ ## 9. Lightweight Clients
469
459
 
470
- ### Intent
471
- - Provide a tiny "client" type made of async closures.
472
- - Keep business logic in a store or feature layer, not the view.
473
- - Enable easy stubbing in previews/tests.
460
+ A client can be a plain struct of async closures. There is no view model and
461
+ no DI framework, the view never sees networking, and previews or tests swap in
462
+ a stub. Business logic sits in a store or feature layer.
474
463
 
475
- ### Minimal shape
476
464
  ```swift
477
- struct SomeClient {
478
- var fetchItems: (_ limit: Int) async throws -> [Item]
479
- var search: (_ query: String, _ limit: Int) async throws -> [Item]
480
- }
481
-
482
- extension SomeClient {
483
- static func live(baseURL: URL = URL(string: "https://example.com")!) -> SomeClient {
484
- let session = URLSession.shared // Prototyping only. For production, create a URLSession with timeoutIntervalForRequest: 30, timeoutIntervalForResource: 300, waitsForConnectivity: true, and a URLCache.
485
- return SomeClient(
486
- fetchItems: { limit in
487
- // build URL, call session, decode
465
+ struct CatalogClient {
466
+ var products: (_ page: Int) async throws -> [Product]
467
+ var lookup: (_ term: String, _ page: Int) async throws -> [Product]
468
+
469
+ static func live(host: URL) -> CatalogClient {
470
+ let session = URLSession(configuration: .catalog)
471
+ return CatalogClient(
472
+ products: { page in
473
+ let url = host.appending(path: "products")
474
+ .appending(queryItems: [URLQueryItem(name: "page", value: String(page))])
475
+ let (data, _) = try await session.data(from: url)
476
+ return try JSONDecoder().decode([Product].self, from: data)
488
477
  },
489
- search: { query, limit in
490
- // build URL, call session, decode
478
+ lookup: { term, page in
479
+ let url = host.appending(path: "search")
480
+ .appending(queryItems: [
481
+ URLQueryItem(name: "q", value: term),
482
+ URLQueryItem(name: "page", value: String(page))
483
+ ])
484
+ let (data, _) = try await session.data(from: url)
485
+ return try JSONDecoder().decode([Product].self, from: data)
491
486
  }
492
487
  )
493
488
  }
494
489
  }
490
+
491
+ extension URLSessionConfiguration {
492
+ static var catalog: URLSessionConfiguration {
493
+ let config = URLSessionConfiguration.default
494
+ config.timeoutIntervalForRequest = 30
495
+ config.timeoutIntervalForResource = 300
496
+ config.waitsForConnectivity = true
497
+ config.urlCache = URLCache(memoryCapacity: 10_000_000, diskCapacity: 50_000_000)
498
+ return config
499
+ }
500
+ }
495
501
  ```
496
502
 
497
- ### Usage pattern
503
+ `URLSession.shared` is fine for a prototype. Production code builds its own
504
+ session as above: 30 second request timeout, 300 second resource timeout,
505
+ `waitsForConnectivity` on, and a `URLCache`.
506
+
498
507
  ```swift
499
- @MainActor
500
- @Observable final class ItemsStore {
501
- enum LoadState { case idle, loading, loaded, failed(String) }
508
+ @MainActor @Observable
509
+ final class CatalogStore {
510
+ enum Status { case idle, loading, loaded, failed(String) }
502
511
 
503
- var items: [Item] = []
504
- var state: LoadState = .idle
505
- private let client: SomeClient
512
+ var status: Status = .idle
513
+ var products: [Product] = []
514
+ private let client: CatalogClient
506
515
 
507
- init(client: SomeClient) {
508
- self.client = client
509
- }
516
+ init(client: CatalogClient) { self.client = client }
510
517
 
511
- func load(limit: Int = 20) async {
512
- state = .loading
518
+ func load(page: Int = 1) async {
519
+ status = .loading
513
520
  do {
514
- items = try await client.fetchItems(limit)
515
- state = .loaded
521
+ products = try await client.products(page)
522
+ status = .loaded
516
523
  } catch {
517
- state = .failed(error.localizedDescription)
524
+ status = .failed(error.localizedDescription)
518
525
  }
519
526
  }
520
527
  }
521
- ```
522
528
 
523
- ```swift
524
- struct ContentView: View {
525
- @Environment(ItemsStore.self) private var store
529
+ struct CatalogView: View {
530
+ @Environment(CatalogStore.self) private var store
526
531
 
527
532
  var body: some View {
528
- List(store.items) { item in
529
- Text(item.title)
530
- }
531
- .task { await store.load() }
533
+ List(store.products) { ProductRow(product: $0) }
534
+ .task { await store.load() }
532
535
  }
533
536
  }
534
- ```
535
537
 
536
- ```swift
537
538
  @main
538
- struct MyApp: App {
539
- @State private var store = ItemsStore(client: .live())
539
+ struct ShopApp: App {
540
+ @State private var store = CatalogStore(client: .live(host: AppConfig.apiHost))
540
541
 
541
542
  var body: some Scene {
542
543
  WindowGroup {
543
- ContentView()
544
- .environment(store)
544
+ CatalogView().environment(store)
545
545
  }
546
546
  }
547
547
  }
548
548
  ```
549
549
 
550
- ### Guidance
551
- - Keep decoding and URL-building in the client; keep state changes in the store.
552
- - Make the store accept the client in `init` and keep it private.
553
- - Avoid global singletons; use `.environment` for store injection.
554
- - If you need multiple variants (mock/stub), add `static func mock(...)`.
550
+ Guidelines:
551
+
552
+ - URL building and decoding live in the client. State changes live in the
553
+ store.
554
+ - The store receives the client in `init` and keeps it `private`.
555
+ - No global singletons for stores; install them with `.environment`.
556
+ - Add `static func mock(...)` factories for stubbed variants.
557
+
558
+ Pitfalls:
559
+
560
+ - UI state kept in the client. It belongs in the store.
561
+ - Client closures that capture `self` or view state.
562
+
563
+ ## 10. Further Reading
555
564
 
556
- ### Pitfalls
557
- - Don't put UI state in the client; keep state in the store.
558
- - Don't capture `self` or view state in the client closures.
565
+ - "SwiftUI in 2025: Forget MVVM" by Thomas Ricouard, the article this MV
566
+ guidance follows.
567
+ - The WWDC data-flow sessions listed in [Why Not MVVM](#2-why-not-mvvm).