@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,415 +1,304 @@
1
- # WidgetKit Advanced Reference
1
+ # WidgetKit: extended patterns
2
+
3
+ Companion to the `widgetkit` skill. Everything here assumes iOS 26 SDKs unless a
4
+ heading says otherwise.
2
5
 
3
6
  ## Contents
4
7
 
5
8
  - [Timeline Strategies](#timeline-strategies)
6
- - [Push-Based Timeline Reloads (iOS 26+)](#push-based-timeline-reloads-ios-26)
7
- - [Widget URL Handling and Deep Links](#widget-url-handling-and-deep-links)
8
- - [Intent-Driven Widget Configuration](#intent-driven-widget-configuration)
9
- - [Multiple Widget Support in WidgetBundle](#multiple-widget-support-in-widgetbundle)
10
- - [Widget Previews and Snapshots](#widget-previews-and-snapshots)
9
+ - [Reloading timelines by push (iOS 26 and later)](#reloading-timelines-by-push-ios-26-and-later)
10
+ - [Deep Links and Widget URLs](#deep-links-and-widget-urls)
11
+ - [User-configurable widgets through App Intents](#user-configurable-widgets-through-app-intents)
12
+ - [Multiple Widget Support](#multiple-widget-support)
13
+ - [Previewing widgets and their snapshot state](#previewing-widgets-and-their-snapshot-state)
11
14
  - [AccessoryWidgetBackground](#accessorywidgetbackground)
12
15
  - [Lock Screen Accessory Widget Example](#lock-screen-accessory-widget-example)
13
16
  - [Live Activity Full ActivityConfiguration Example](#live-activity-full-activityconfiguration-example)
14
17
  - [Control Center Control Examples](#control-center-control-examples)
15
- - [Dynamic Island Expanded Layout Patterns](#dynamic-island-expanded-layout-patterns)
16
- - [Alert Configuration for Live Activities](#alert-configuration-for-live-activities)
17
- - [Push Notification Support for Live Activities](#push-notification-support-for-live-activities)
18
+ - [Laying out the expanded Dynamic Island](#laying-out-the-expanded-dynamic-island)
19
+ - [Live Activity alerts](#live-activity-alerts)
20
+ - [Live Activity Push Updates](#live-activity-push-updates)
18
21
  - [ActivityAuthorizationInfo](#activityauthorizationinfo)
19
- - [Widget Performance Best Practices](#widget-performance-best-practices)
22
+ - [Keeping widgets fast](#keeping-widgets-fast)
20
23
  - [Xcode Setup](#xcode-setup)
21
- - [Widget Relevance and Smart Stacks](#widget-relevance-and-smart-stacks)
24
+ - [Smart Stack relevance](#smart-stack-relevance)
22
25
  - [ActivityState Lifecycle](#activitystate-lifecycle)
23
26
  - [ActivityStyle](#activitystyle)
24
27
  - [Dismissal Policies](#dismissal-policies)
25
- - [Querying Active Widgets and Activities](#querying-active-widgets-and-activities)
28
+ - [Finding which widgets and activities are live](#finding-which-widgets-and-activities-are-live)
26
29
  - [Design Patterns](#design-patterns)
27
- - [Apple Documentation Links](#apple-documentation-links)
30
+ - [Further reading from Apple](#further-reading-from-apple)
28
31
 
29
32
  ## Timeline Strategies
30
33
 
31
- ### TimelineReloadPolicy
32
-
33
- Control when WidgetKit requests a new timeline after the current entries expire.
34
+ ### Reload policies
34
35
 
35
- | Policy | Behavior | Use When |
36
+ | Policy | What happens | Pick it when |
36
37
  |---|---|---|
37
- | `.atEnd` | Requests a new timeline after the last entry's date. Default. | Data changes unpredictably. |
38
- | `.after(Date)` | Requests a new timeline after a specific date. | Data updates on a known schedule (market hours, delivery slots). |
39
- | `.never` | No automatic refresh. App must trigger manually. | Data changes only from user action. |
38
+ | `.atEnd` | The system asks for a new timeline once the last entry's date passes. This is the default. | Data changes at unpredictable times |
39
+ | `.after(Date)` | The system asks again after the given date | You know the schedule, such as trading hours or a booked delivery slot |
40
+ | `.never` | No automatic refresh; the app reloads it | Only a user action in the app changes the data |
40
41
 
41
- ### Multiple Timeline Entries
42
+ ### Plan several entries ahead
42
43
 
43
- Pre-generate entries for known future states to reduce refresh requests and
44
- conserve the daily budget.
44
+ One timeline can carry many future entries. Projecting values forward means
45
+ fewer reloads.
45
46
 
46
47
  ```swift
47
- func timeline(for configuration: Intent, in context: Context) async -> Timeline<StockEntry> {
48
- var entries: [StockEntry] = []
49
- let now = Date()
50
-
51
- // Generate hourly entries for the next 6 hours
52
- for hourOffset in 0..<6 {
53
- let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: now)!
54
- let price = await StockService.shared.projectedPrice(at: entryDate, for: configuration.symbol)
55
- entries.append(StockEntry(date: entryDate, symbol: configuration.symbol.name, price: price))
48
+ func getTimeline(in context: Context, completion: @escaping @Sendable (Timeline<ParkingEntry>) -> Void) {
49
+ let base = ParkingForecast.current()
50
+ let calendar = Calendar.current
51
+ let entries = (0..<6).compactMap { offset -> ParkingEntry? in
52
+ guard let slot = calendar.date(byAdding: .hour, value: offset, to: .now) else { return nil }
53
+ return ParkingEntry(date: slot, freeSpaces: base.projectedSpaces(at: slot))
56
54
  }
57
-
58
- let nextRefresh = Calendar.current.date(byAdding: .hour, value: 6, to: now)!
59
- return Timeline(entries: entries, policy: .after(nextRefresh))
55
+ let refresh = calendar.date(byAdding: .minute, value: 360, to: .now) ?? .now
56
+ completion(Timeline(entries: entries, policy: .after(refresh)))
60
57
  }
61
58
  ```
62
59
 
63
- ### Triggering Manual Reloads
60
+ ### Reloading from the app
64
61
 
65
62
  ```swift
66
- // Reload a specific widget kind
67
- WidgetCenter.shared.reloadTimelines(ofKind: "OrderStatusWidget")
68
-
69
- // Reload all widgets
63
+ WidgetCenter.shared.reloadTimelines(ofKind: "ParkingWidget")
70
64
  WidgetCenter.shared.reloadAllTimelines()
71
65
  ```
72
66
 
73
- Call `reloadTimelines(ofKind:)` only when displayed data actually changes. Each
74
- call counts against the daily refresh budget.
75
-
76
- ### Refresh Budget
77
-
78
- Each configured widget has a daily refresh limit. Exemptions apply for:
79
- - Foreground app usage
80
- - Active media sessions
81
- - Standard location service usage
67
+ The first call reloads one widget kind; the second reloads all of them. Both
68
+ cost budget.
82
69
 
83
- WidgetKit does not impose refresh limits when debugging in Xcode.
70
+ ### Budget rules
84
71
 
85
- ## Push-Based Timeline Reloads (iOS 26+)
72
+ - Every configured widget gets its own daily allowance.
73
+ - Reloads are free when the app is frontmost, when it has an active media
74
+ session, or when it uses the standard location service.
75
+ - A widget run from Xcode's debugger has no refresh limit, so budget problems
76
+ only show up outside the debugger.
86
77
 
87
- ### WidgetPushHandler
78
+ ## Reloading timelines by push (iOS 26 and later)
88
79
 
89
- Use push notifications to trigger timeline reloads without scheduled polling.
80
+ A `WidgetPushHandler` receives a token for APNs. Send it to your server.
90
81
 
91
82
  ```swift
92
- struct MyWidgetPushHandler: WidgetPushHandler {
83
+ struct ScoreboardPushHandler: WidgetPushHandler {
93
84
  func pushTokenDidChange(_ pushInfo: WidgetPushInfo, widgets: [WidgetInfo]) {
94
- let tokenString = pushInfo.token.map { String(format: "%02x", $0) }.joined()
95
- Task {
96
- try await ServerAPI.shared.register(widgetPushToken: tokenString)
97
- }
85
+ let hex = hexString(pushInfo.token)
86
+ Task { await ScoreboardAPI.shared.registerWidgetToken(hex) }
98
87
  }
99
88
  }
100
89
  ```
101
90
 
102
- ### Server-Side Integration
103
-
104
- Send an APNs push with the widget's push token. The system calls your
105
- `TimelineProvider.getTimeline` or `AppIntentTimelineProvider.timeline(for:in:)`
106
- when the push arrives.
107
-
108
- ### ControlPushHandler
109
-
110
- Equivalent handler for Control Center controls:
91
+ `hexString(_:)` is a small helper used throughout this file:
111
92
 
112
93
  ```swift
113
- struct MyControlPushHandler: ControlPushHandler {
114
- func pushTokensDidChange(controls: [ControlPushInfo]) {
115
- for control in controls {
116
- let tokenString = control.token.map { String(format: "%02x", $0) }.joined()
117
- Task {
118
- try await ServerAPI.shared.register(controlPushToken: tokenString)
119
- }
120
- }
121
- }
94
+ func hexString(_ bytes: Data) -> String {
95
+ bytes.reduce(into: "") { $0 += String(format: "%02x", $1) }
122
96
  }
123
97
  ```
124
98
 
125
- ## Widget URL Handling and Deep Links
126
-
127
- ### widgetURL(_:)
99
+ When the server sends an APNs push addressed to that token, the system calls
100
+ `getTimeline` or `timeline(for:in:)` again.
128
101
 
129
- Set a single URL for the entire widget. Tapping anywhere opens the app with this URL.
102
+ Controls have their own handler that receives every control at once, as
103
+ `ControlInfo` values; each carries its token in an optional `pushInfo`:
130
104
 
131
105
  ```swift
132
- struct SmallWidgetView: View {
133
- let entry: OrderEntry
134
-
135
- var body: some View {
136
- VStack {
137
- Text(entry.orderName)
138
- Text(entry.status)
106
+ struct GaragePushHandler: ControlPushHandler {
107
+ func pushTokensDidChange(controls: [ControlInfo]) {
108
+ for control in controls {
109
+ guard let token = control.pushInfo?.token else { continue }
110
+ let hex = hexString(token)
111
+ Task { await GarageAPI.shared.registerControlToken(hex) }
139
112
  }
140
- .widgetURL(URL(string: "myapp://orders/\(entry.orderID)")!)
141
113
  }
142
114
  }
143
115
  ```
144
116
 
145
- ### Link (Medium and Larger Widgets)
117
+ ## Deep Links and Widget URLs
146
118
 
147
- Use `Link` for multiple tap targets in `.systemMedium` and larger widgets.
119
+ - `.widgetURL(_:)` gives the whole widget one destination.
120
+ - `Link(destination:)` adds separate tap targets, but only in `.systemMedium` and
121
+ larger. A `.systemSmall` widget supports `widgetURL` only.
148
122
 
149
123
  ```swift
150
- struct MediumWidgetView: View {
151
- let entry: OrderListEntry
152
-
153
- var body: some View {
154
- VStack {
155
- ForEach(entry.orders) { order in
156
- Link(destination: URL(string: "myapp://orders/\(order.id)")!) {
157
- HStack {
158
- Text(order.name)
159
- Spacer()
160
- Text(order.status)
161
- }
162
- }
163
- }
164
- }
165
- }
124
+ VStack {
125
+ Link(destination: URL(string: "harbor://berth/7")!) { BerthRow(number: 7) }
126
+ Link(destination: URL(string: "harbor://berth/9")!) { BerthRow(number: 9) }
166
127
  }
128
+ .widgetURL(URL(string: "harbor://berths"))
167
129
  ```
168
130
 
169
- ### Handling in the App
131
+ The app picks the URL up on its scene content:
170
132
 
171
133
  ```swift
172
- @main
173
- struct MyApp: App {
174
- var body: some Scene {
175
- WindowGroup {
176
- ContentView()
177
- .onOpenURL { url in
178
- DeepLinkRouter.shared.handle(url)
179
- }
180
- }
181
- }
134
+ WindowGroup {
135
+ HarborRootView()
136
+ .onOpenURL { url in router.open(url) }
182
137
  }
183
138
  ```
184
139
 
185
- **Important:** `.systemSmall` widgets support only `widgetURL`, not `Link`.
186
-
187
- ## Intent-Driven Widget Configuration
188
-
189
- ### Defining a WidgetConfigurationIntent
140
+ ## User-configurable widgets through App Intents
190
141
 
191
142
  ```swift
192
- struct SelectCategoryIntent: WidgetConfigurationIntent {
193
- static var title: LocalizedStringResource = "Select Category"
194
- static var description: IntentDescription = "Choose a category to display."
143
+ struct PickStationIntent: WidgetConfigurationIntent {
144
+ static let title: LocalizedStringResource = "Choose Station"
145
+ static let description = IntentDescription("Pick the weather station to show.")
195
146
 
196
- @Parameter(title: "Category")
197
- var category: CategoryEntity
147
+ @Parameter(title: "Station")
148
+ var station: StationEntity
198
149
 
199
150
  init() {}
200
-
201
- init(category: CategoryEntity) {
202
- self.category = category
203
- }
151
+ init(station: StationEntity) { self.station = station }
204
152
  }
205
- ```
206
153
 
207
- ### Entity Query for Dynamic Options
154
+ struct StationEntity: AppEntity {
155
+ static let typeDisplayRepresentation: TypeDisplayRepresentation = "Station"
156
+ static let defaultQuery = StationQuery()
208
157
 
209
- ```swift
210
- struct CategoryEntity: AppEntity {
211
- static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Category")
212
- static var defaultQuery = CategoryQuery()
213
-
214
- var id: String
215
- var name: String
158
+ let id: String
159
+ let name: String
216
160
 
217
161
  var displayRepresentation: DisplayRepresentation {
218
- DisplayRepresentation(title: "\(name)")
162
+ DisplayRepresentation(title: LocalizedStringResource(stringLiteral: name))
219
163
  }
220
164
  }
221
165
 
222
- struct CategoryQuery: EntityQuery {
223
- func entities(for identifiers: [String]) async throws -> [CategoryEntity] {
224
- await DataStore.shared.categories(for: identifiers)
166
+ struct StationQuery: EntityQuery {
167
+ func entities(for identifiers: [String]) async throws -> [StationEntity] {
168
+ await StationCatalog.shared.stations(matching: identifiers)
225
169
  }
226
170
 
227
- func suggestedEntities() async throws -> [CategoryEntity] {
228
- await DataStore.shared.allCategories()
171
+ func suggestedEntities() async throws -> [StationEntity] {
172
+ await StationCatalog.shared.nearby()
229
173
  }
230
174
 
231
- func defaultResult() async -> CategoryEntity? {
232
- await DataStore.shared.defaultCategory()
175
+ func defaultResult() async -> StationEntity? {
176
+ await StationCatalog.shared.nearby().first
233
177
  }
234
178
  }
235
179
  ```
236
180
 
237
- ### Recommendations
238
-
239
- Provide pre-configured suggestions for the widget gallery:
181
+ Preconfigured gallery options come from the provider:
240
182
 
241
183
  ```swift
242
- func recommendations() -> [AppIntentRecommendation<SelectCategoryIntent>] {
243
- let categories: [(String, CategoryEntity)] = [
244
- ("Groceries", .groceries),
245
- ("Work Tasks", .work),
246
- ]
247
- return categories.map { name, entity in
248
- let intent = SelectCategoryIntent(category: entity)
249
- return AppIntentRecommendation(intent: intent, description: name)
184
+ func recommendations() -> [AppIntentRecommendation<PickStationIntent>] {
185
+ StationCatalog.featured.map { station in
186
+ AppIntentRecommendation(intent: PickStationIntent(station: station), description: station.name)
250
187
  }
251
188
  }
252
189
  ```
253
190
 
254
- ## Multiple Widget Support in WidgetBundle
191
+ For intent and entity design beyond this, see the `app-intents` skill.
255
192
 
256
- ### Declaring Multiple Widgets
193
+ ## Multiple Widget Support
257
194
 
258
- ```swift
259
- @main
260
- struct MyAppWidgets: WidgetBundle {
261
- var body: some Widget {
262
- OrderStatusWidget() // Home Screen widget
263
- FavoritesWidget() // Configurable widget
264
- StepsAccessoryWidget() // Lock Screen widget
265
- DeliveryActivityWidget() // Live Activity
266
- QuickActionControl() // Control Center
267
- }
268
- }
269
- ```
270
-
271
- ### Conditional Widgets
272
-
273
- Include widgets conditionally based on platform or availability:
195
+ A bundle can mix every kind of widget, and it can gate newer ones by OS:
274
196
 
275
197
  ```swift
276
198
  @main
277
- struct MyAppWidgets: WidgetBundle {
199
+ struct GardenWidgets: WidgetBundle {
278
200
  var body: some Widget {
279
- CoreWidget()
201
+ SoilMoistureWidget()
202
+ PickBedWidget()
203
+ SoilLockScreenWidget()
204
+ WateringActivityWidget()
280
205
  if #available(iOS 18, *) {
281
- QuickActionControl()
206
+ SprinklerControl()
282
207
  }
283
208
  }
284
209
  }
285
210
  ```
286
211
 
287
- ## Widget Previews and Snapshots
288
-
289
- ### Xcode Previews
212
+ ## Previewing widgets and their snapshot state
290
213
 
291
214
  ```swift
292
- #Preview("Small", as: .systemSmall) {
293
- OrderStatusWidget()
215
+ #Preview("Moisture", as: .systemSmall) {
216
+ SoilMoistureWidget()
294
217
  } timeline: {
295
- OrderEntry(date: .now, orderName: "Pizza", status: "Preparing")
296
- OrderEntry(date: .now.addingTimeInterval(600), orderName: "Pizza", status: "Delivering")
218
+ SoilEntry(date: .now, percent: 62)
219
+ SoilEntry(date: .now.addingTimeInterval(3600), percent: 48)
297
220
  }
298
221
 
299
- #Preview("Circular", as: .accessoryCircular) {
300
- StepsAccessoryWidget()
222
+ #Preview("Lock Screen", as: .accessoryCircular) {
223
+ SoilLockScreenWidget()
301
224
  } timeline: {
302
- StepsEntry(date: .now, stepCount: 4200)
225
+ SoilEntry(date: .now, percent: 62)
303
226
  }
304
227
  ```
305
228
 
306
- ### Live Activity Previews
229
+ Live Activities preview against attributes plus a list of states:
307
230
 
308
231
  ```swift
309
- #Preview("Lock Screen", as: .content, using: DeliveryAttributes.preview) {
310
- DeliveryActivityWidget()
232
+ #Preview("Watering", as: .content, using: WateringAttributes.preview) {
233
+ WateringActivityWidget()
311
234
  } contentStates: {
312
- DeliveryAttributes.ContentState(
313
- driverName: "Alex",
314
- estimatedDeliveryTime: Date()...Date().addingTimeInterval(900),
315
- currentStep: .delivering
316
- )
235
+ WateringAttributes.ContentState(zone: 1, remaining: 300)
236
+ WateringAttributes.ContentState(zone: 2, remaining: 60)
317
237
  }
318
238
 
319
- #Preview("Dynamic Island Compact", as: .dynamicIsland(.compact), using: DeliveryAttributes.preview) {
320
- DeliveryActivityWidget()
239
+ #Preview("Compact", as: .dynamicIsland(.compact), using: WateringAttributes.preview) {
240
+ WateringActivityWidget()
321
241
  } contentStates: {
322
- DeliveryAttributes.ContentState(
323
- driverName: "Alex",
324
- estimatedDeliveryTime: Date()...Date().addingTimeInterval(900),
325
- currentStep: .delivering
326
- )
242
+ WateringAttributes.ContentState(zone: 1, remaining: 300)
327
243
  }
328
244
  ```
329
245
 
330
- ### Snapshot Best Practices
246
+ `placeholder(in:)` is synchronous, so writing `await` inside it does not
247
+ compile. In the snapshot method, return sample data when `context.isPreview` is
248
+ true and the real current state otherwise.
331
249
 
332
- - Return sample data immediately in `placeholder(in:)` -- it must be synchronous.
333
- - In `getSnapshot` / `snapshot(for:in:)`, check `context.isPreview`:
334
- - When `true`, return representative sample data quickly.
335
- - When `false`, return the current real state.
250
+ ## AccessoryWidgetBackground
336
251
 
337
252
  ```swift
338
- // WRONG: Performing a network call in placeholder
339
- func placeholder(in context: Context) -> MyEntry {
340
- // Compilation error: placeholder must be synchronous
341
- let data = await fetchData()
342
- return MyEntry(date: .now, data: data)
343
- }
344
-
345
- // CORRECT: Return static sample data
346
- func placeholder(in context: Context) -> MyEntry {
347
- MyEntry(date: .now, data: SampleData.placeholder)
253
+ ZStack {
254
+ AccessoryWidgetBackground()
255
+ VStack(spacing: 0) {
256
+ Image(systemName: "leaf")
257
+ .widgetAccentable()
258
+ Text("\(entry.percent)%")
259
+ }
348
260
  }
349
261
  ```
350
262
 
351
- ## AccessoryWidgetBackground
263
+ `AccessoryWidgetBackground()` draws the standard translucent disc or panel.
264
+ `.widgetAccentable()` marks the views that should pick up the tint in `.accented`
265
+ mode.
352
266
 
353
- Provide the standard translucent background for Lock Screen widgets.
267
+ Branch on the rendering mode when full color and monochrome need different views.
268
+ `WidgetRenderingMode` is a struct with static values, not an enum, so the switch
269
+ ends in a plain `default` (`@unknown default` does not compile here):
354
270
 
355
271
  ```swift
356
- struct CircularStepsView: View {
357
- let steps: Int
272
+ struct SoilBadge: View {
273
+ @Environment(\.widgetRenderingMode) private var mode
274
+ let entry: SoilEntry
358
275
 
359
276
  var body: some View {
360
- ZStack {
361
- AccessoryWidgetBackground()
362
- VStack(spacing: 2) {
363
- Image(systemName: "figure.walk")
364
- .font(.caption)
365
- Text("\(steps)")
366
- .font(.headline)
367
- .widgetAccentable()
368
- }
277
+ switch mode {
278
+ case .fullColor:
279
+ ColorfulSoilBadge(entry: entry)
280
+ case .vibrant, .accented:
281
+ MonochromeSoilBadge(entry: entry)
282
+ default:
283
+ MonochromeSoilBadge(entry: entry)
369
284
  }
370
285
  }
371
286
  }
372
287
  ```
373
288
 
374
- ### Rendering Mode Awareness
375
-
376
- Lock Screen widgets render in `.vibrant` or `.accented` mode. Adapt content:
377
-
378
- ```swift
379
- @Environment(\.widgetRenderingMode) var renderingMode
380
-
381
- var body: some View {
382
- switch renderingMode {
383
- case .fullColor:
384
- ColorfulView()
385
- case .vibrant, .accented:
386
- MonochromeView()
387
- @unknown default:
388
- MonochromeView()
389
- }
390
- }
391
- ```
392
-
393
- Use `.widgetAccentable()` to mark views that should receive the accent tint in
394
- `.accented` rendering mode.
395
-
396
289
  ## Lock Screen Accessory Widget Example
397
290
 
398
- A full Lock Screen widget using accessory families and `AccessoryWidgetBackground`.
399
-
400
291
  ```swift
401
- struct StepsWidget: Widget {
402
- let kind = "StepsWidget"
292
+ struct SoilLockScreenWidget: Widget {
403
293
  var body: some WidgetConfiguration {
404
- StaticConfiguration(kind: kind, provider: StepsProvider()) { entry in
294
+ StaticConfiguration(kind: "SoilLockScreen", provider: SoilProvider()) { entry in
405
295
  ZStack {
406
296
  AccessoryWidgetBackground()
407
- VStack {
408
- Image(systemName: "figure.walk")
409
- Text("\(entry.stepCount)").font(.headline)
410
- }
297
+ SoilBadge(entry: entry)
411
298
  }
412
299
  }
300
+ .configurationDisplayName("Soil")
301
+ .description("Moisture of your main bed.")
413
302
  .supportedFamilies([.accessoryCircular, .accessoryRectangular, .accessoryInline])
414
303
  }
415
304
  }
@@ -417,661 +306,467 @@ struct StepsWidget: Widget {
417
306
 
418
307
  ## Live Activity Full ActivityConfiguration Example
419
308
 
420
- A complete `ActivityConfiguration` with Lock Screen content and the Dynamic
421
- Island closures (expanded regions, compact, minimal).
422
-
423
309
  ```swift
424
- struct DeliveryActivityWidget: Widget {
310
+ struct RepairActivityWidget: Widget {
425
311
  var body: some WidgetConfiguration {
426
- ActivityConfiguration(for: DeliveryAttributes.self) { context in
312
+ ActivityConfiguration(for: RepairAttributes.self) { context in
427
313
  VStack(alignment: .leading) {
428
- Text(context.attributes.restaurantName).font(.headline)
429
- HStack {
430
- Text("Driver: \(context.state.driverName)")
431
- Spacer()
432
- Text(timerInterval: context.state.estimatedDeliveryTime, countsDown: true)
433
- }
314
+ Text("Ticket \(context.attributes.ticketNumber)")
315
+ .font(.headline)
316
+ Text(context.state.stage.title)
317
+ Text(timerInterval: context.state.window, countsDown: true)
318
+ .font(.title2.monospacedDigit())
434
319
  }
435
320
  .padding()
436
321
  } dynamicIsland: { context in
437
322
  DynamicIsland {
438
323
  DynamicIslandExpandedRegion(.leading) {
439
- Image(systemName: "box.truck.fill").font(.title2)
324
+ Image(systemName: "wrench.and.screwdriver")
440
325
  }
441
326
  DynamicIslandExpandedRegion(.trailing) {
442
- Text(timerInterval: context.state.estimatedDeliveryTime, countsDown: true)
443
- .font(.caption)
327
+ Text(timerInterval: context.state.window, countsDown: true)
328
+ .monospacedDigit()
444
329
  }
445
330
  DynamicIslandExpandedRegion(.center) {
446
- Text(context.attributes.restaurantName).font(.headline)
331
+ Text(context.state.stage.title)
332
+ .lineLimit(1)
447
333
  }
448
334
  DynamicIslandExpandedRegion(.bottom) {
449
335
  HStack {
450
- ForEach(DeliveryStep.allCases, id: \.self) { step in
451
- Image(systemName: step.icon)
452
- .foregroundStyle(step <= context.state.currentStep ? .primary : .tertiary)
336
+ ForEach(RepairStage.allCases, id: \.self) { stage in
337
+ Capsule()
338
+ .fill(stage <= context.state.stage ? .primary : .tertiary)
339
+ .frame(height: 4)
453
340
  }
454
341
  }
455
342
  }
456
343
  } compactLeading: {
457
- Image(systemName: "box.truck.fill")
344
+ Image(systemName: "wrench.and.screwdriver")
458
345
  } compactTrailing: {
459
- Text(timerInterval: context.state.estimatedDeliveryTime, countsDown: true)
460
- .frame(width: 40).monospacedDigit()
346
+ Text(timerInterval: context.state.window, countsDown: true)
347
+ .monospacedDigit()
348
+ .frame(width: 44)
461
349
  } minimal: {
462
- Image(systemName: "box.truck.fill")
350
+ Image(systemName: "wrench")
463
351
  }
464
352
  }
465
353
  }
466
354
  }
467
355
  ```
468
356
 
357
+ The compact trailing timer gets a fixed width and monospaced digits so it does
358
+ not jitter as it counts. `RepairStage` is assumed `Comparable` and `CaseIterable`.
359
+
469
360
  ## Control Center Control Examples
470
361
 
471
- Button and toggle controls for Control Center (iOS 18+). A toggle can read its
472
- current state from a value provider.
362
+ A toggle reads current state from a value provider and flips it through an intent:
473
363
 
474
364
  ```swift
475
- // Button control
476
- struct OpenCameraControl: ControlWidget {
365
+ struct SprinklerControl: ControlWidget {
477
366
  var body: some ControlWidgetConfiguration {
478
- StaticControlConfiguration(kind: "OpenCamera") {
479
- ControlWidgetButton(action: OpenCameraIntent()) {
480
- Label("Camera", systemImage: "camera.fill")
367
+ StaticControlConfiguration(kind: "SprinklerControl", provider: SprinklerStateProvider()) { isRunning in
368
+ ControlWidgetToggle(isOn: isRunning, action: ToggleSprinklerIntent()) {
369
+ Label("Sprinkler", systemImage: isRunning ? "sprinkler.and.droplets.fill" : "sprinkler")
481
370
  }
482
371
  }
483
- .displayName("Open Camera")
372
+ .displayName("Sprinkler")
484
373
  }
485
374
  }
486
375
 
487
- // Toggle control with value provider
488
- struct FlashlightControl: ControlWidget {
489
- var body: some ControlWidgetConfiguration {
490
- StaticControlConfiguration(kind: "Flashlight", provider: FlashlightValueProvider()) { value in
491
- ControlWidgetToggle(isOn: value, action: ToggleFlashlightIntent()) {
492
- Label("Flashlight", systemImage: value ? "flashlight.on.fill" : "flashlight.off.fill")
493
- }
494
- }
495
- .displayName("Flashlight")
376
+ struct SprinklerStateProvider: ControlValueProvider {
377
+ var previewValue: Bool { false }
378
+
379
+ func currentValue() async throws -> Bool {
380
+ await SprinklerHub.shared.isRunning
381
+ }
382
+ }
383
+
384
+ struct ToggleSprinklerIntent: SetValueIntent {
385
+ static let title: LocalizedStringResource = "Toggle Sprinkler"
386
+
387
+ @Parameter(title: "Running")
388
+ var value: Bool
389
+
390
+ func perform() async throws -> some IntentResult {
391
+ await SprinklerHub.shared.setRunning(value)
392
+ return .result()
496
393
  }
497
394
  }
498
395
  ```
499
396
 
500
- ## Dynamic Island Expanded Layout Patterns
397
+ ## Laying out the expanded Dynamic Island
501
398
 
502
- ### Full Layout Example
399
+ A ferry crossing shows how the regions split the work:
503
400
 
504
401
  ```swift
505
402
  DynamicIsland {
506
403
  DynamicIslandExpandedRegion(.leading) {
507
404
  VStack(alignment: .leading) {
508
- Image(systemName: "airplane")
509
- .font(.title2)
510
- Text("UA 1234")
511
- .font(.caption2)
405
+ Text(context.attributes.fromPort).font(.headline)
406
+ Text(context.state.departure, style: .time).font(.caption)
512
407
  }
513
408
  }
514
409
  DynamicIslandExpandedRegion(.trailing) {
515
410
  VStack(alignment: .trailing) {
516
- Text("SFO")
517
- .font(.title3.bold())
518
- Text("On Time")
519
- .font(.caption2)
520
- .foregroundStyle(.green)
411
+ Text(context.attributes.toPort).font(.headline)
412
+ Text(context.state.arrival, style: .time).font(.caption)
521
413
  }
522
414
  }
523
415
  DynamicIslandExpandedRegion(.center) {
524
- Text("San Francisco to New York")
525
- .font(.caption)
526
- .lineLimit(1)
416
+ Text(context.state.statusLine).lineLimit(1)
527
417
  }
528
418
  DynamicIslandExpandedRegion(.bottom) {
529
- ProgressView(value: 0.45)
530
- .tint(.blue)
531
- HStack {
532
- Text("Departed 2:30 PM")
533
- Spacer()
534
- Text("Arrives 10:45 PM")
535
- }
536
- .font(.caption2)
537
- .foregroundStyle(.secondary)
419
+ ProgressView(value: context.state.progress)
538
420
  }
539
421
  } compactLeading: {
540
- Image(systemName: "airplane")
422
+ Image(systemName: "ferry")
541
423
  } compactTrailing: {
542
- Text("2h 15m")
543
- .monospacedDigit()
424
+ Text(context.state.arrival, style: .timer).monospacedDigit()
544
425
  } minimal: {
545
- Image(systemName: "airplane")
426
+ Image(systemName: "ferry")
546
427
  }
547
- ```
548
-
549
- ### Vertical Placement
550
-
551
- Control vertical alignment within expanded regions:
552
-
553
- ```swift
554
- DynamicIslandExpandedRegion(.leading) {
555
- Text("Top")
556
- .dynamicIsland(verticalPlacement: .belowIfTooWide)
557
- }
558
- ```
559
-
560
- ### Content Margins
561
-
562
- Override margins for specific Dynamic Island modes:
563
-
564
- ```swift
428
+ .keylineTint(.teal)
565
429
  .contentMargins(.trailing, 20, for: .expanded)
566
430
  .contentMargins(.bottom, 16, for: .expanded)
567
431
  ```
568
432
 
569
- ### Keyline Tint
570
-
571
- Apply a subtle tint to the Dynamic Island border:
572
-
573
- ```swift
574
- DynamicIsland { /* ... */ }
575
- .keylineTint(.blue)
576
- ```
433
+ - `.dynamicIsland(verticalPlacement: .belowIfTooWide)` on a view inside an
434
+ expanded region lets it drop under the camera when it does not fit beside it.
435
+ - `.contentMargins(_:_:for:)` overrides the default margins for one presentation
436
+ mode.
437
+ - `.keylineTint(_:)` on the `DynamicIsland` colors its outline.
577
438
 
578
- ## Alert Configuration for Live Activities
439
+ ## Live Activity alerts
579
440
 
580
- Trigger a visible and audible alert when updating a Live Activity:
441
+ An update can alert the user visibly and audibly:
581
442
 
582
443
  ```swift
583
444
  let alert = AlertConfiguration(
584
- title: "Delivery Update",
585
- body: "Your order is out for delivery!",
445
+ title: "Ferry boarding",
446
+ body: "Gate B is open.",
586
447
  sound: .default
587
448
  )
588
- await activity.update(updatedContent, alertConfiguration: alert)
449
+ await activity.update(content, alertConfiguration: alert)
589
450
  ```
590
451
 
591
- ### Custom Alert Sound
452
+ For a custom sound, bundle the file with the app and pass `.named("horn.aiff")`.
592
453
 
593
- ```swift
594
- let alert = AlertConfiguration(
595
- title: "Score Update",
596
- body: "Goal! The score is now 2-1.",
597
- sound: .named("goal-horn.aiff")
598
- )
599
- ```
600
-
601
- Place the sound file in the app bundle. Use `.default` when no custom sound is needed.
602
-
603
- ## Push Notification Support for Live Activities
454
+ ## Live Activity Push Updates
604
455
 
605
- ### Registering for Push Updates
456
+ Request with `pushType: .token` and forward each token the activity reports.
457
+ `Activity` is a class that is not `Sendable`, so start this task from main-actor
458
+ code such as a view or an `@MainActor` model; in a nonisolated function Swift 6
459
+ rejects capturing `activity` in the `Task`:
606
460
 
607
461
  ```swift
608
- let activity = try Activity.request(
609
- attributes: attributes,
610
- content: content,
611
- pushType: .token // Enable push updates
612
- )
613
-
614
- // Observe token changes
615
462
  Task {
616
- for await token in activity.pushTokenUpdates {
617
- let tokenString = token.map { String(format: "%02x", $0) }.joined()
618
- try await ServerAPI.shared.registerActivityToken(tokenString, activityID: activity.id)
463
+ for await data in activity.pushTokenUpdates {
464
+ let hex = hexString(data)
465
+ await FerryAPI.shared.register(activityToken: hex, activityID: activity.id)
619
466
  }
620
467
  }
621
468
  ```
622
469
 
623
- ### Push-to-Start (Remote Activity Creation)
470
+ Push-to-start tokens come from the activity type:
624
471
 
625
472
  ```swift
626
- // Observe the push-to-start token
627
473
  Task {
628
- for await token in Activity<DeliveryAttributes>.pushToStartTokenUpdates {
629
- let tokenString = token.map { String(format: "%02x", $0) }.joined()
630
- try await ServerAPI.shared.registerPushToStartToken(tokenString)
474
+ for await data in Activity<FerryAttributes>.pushToStartTokenUpdates {
475
+ let hex = hexString(data)
476
+ await FerryAPI.shared.register(startToken: hex)
631
477
  }
632
478
  }
633
479
  ```
634
480
 
635
- ### Channel-Based Push (iOS 26+)
636
-
637
- ```swift
638
- let activity = try Activity.request(
639
- attributes: attributes,
640
- content: content,
641
- pushType: .channel("delivery-updates")
642
- )
643
- ```
481
+ From iOS 26 a broadcast channel works too: `pushType: .channel("ferry-route-12")`.
644
482
 
645
- ### APNs Payload Format for Live Activity Updates
483
+ Update payload:
646
484
 
647
485
  ```json
648
486
  {
649
- "aps": {
650
- "timestamp": 1234567890,
651
- "event": "update",
652
- "content-state": {
653
- "driverName": "Alex",
654
- "estimatedDeliveryTime": {
655
- "lowerBound": 1234567890,
656
- "upperBound": 1234568790
657
- },
658
- "currentStep": "delivering"
659
- },
660
- "alert": {
661
- "title": "Delivery Update",
662
- "body": "Your driver is nearby!"
663
- }
664
- }
487
+ "aps": {
488
+ "timestamp": 1790400000,
489
+ "event": "update",
490
+ "content-state": {
491
+ "statusLine": "Crossing",
492
+ "progress": 0.4,
493
+ "window": { "lowerBound": 1790399400, "upperBound": 1790401800 }
494
+ },
495
+ "alert": { "title": "Halfway", "body": "Arriving in 20 minutes." }
496
+ }
665
497
  }
666
498
  ```
667
499
 
668
- The `content-state` must match the `ContentState` Codable structure exactly.
500
+ `content-state` has to mirror the `ContentState` Codable layout key for key; a
501
+ `ClosedRange` encodes as `lowerBound` and `upperBound`. Info.plist keys on the
502
+ app target:
669
503
 
670
- ### Info.plist Keys
504
+ | Key | Effect |
505
+ |---|---|
506
+ | `NSSupportsLiveActivities` = YES | Allows Live Activities at all |
507
+ | `NSSupportsLiveActivitiesFrequentUpdates` = YES | Raises the push update budget for frequent updates |
671
508
 
672
- | Key | Value | Purpose |
673
- |---|---|---|
674
- | `NSSupportsLiveActivities` | `YES` | Enable Live Activities |
675
- | `NSSupportsLiveActivitiesFrequentUpdates` | `YES` | Enable frequent push updates (budget increase) |
509
+ The `live-activities` skill goes deeper on the push contract.
676
510
 
677
511
  ## ActivityAuthorizationInfo
678
512
 
679
- Check whether Live Activities are permitted before attempting to start one.
680
-
681
513
  ```swift
682
- let authInfo = ActivityAuthorizationInfo()
683
-
684
- // Check permission synchronously
685
- if authInfo.areActivitiesEnabled {
686
- try Activity.request(attributes: attributes, content: content, pushType: .token)
687
- }
514
+ let info = ActivityAuthorizationInfo()
515
+ guard info.areActivitiesEnabled else { return }
688
516
 
689
- // Observe permission changes
690
517
  Task {
691
- for await enabled in authInfo.activityEnablementUpdates {
692
- if enabled {
693
- // Activities became available
694
- }
518
+ for await enabled in info.activityEnablementUpdates {
519
+ await MainActor.run { settings.liveActivitiesOn = enabled }
695
520
  }
696
521
  }
697
522
 
698
- // Check frequent push support
699
- if authInfo.frequentPushesEnabled {
700
- // Safe to use frequent push updates
701
- }
702
- ```
703
-
704
- ### Error Handling
523
+ let canPushOften = info.frequentPushesEnabled
705
524
 
706
- ```swift
707
525
  do {
708
- let activity = try Activity.request(attributes: attributes, content: content, pushType: .token)
526
+ _ = try Activity.request(attributes: crossing, content: opening, pushType: .token)
709
527
  } catch let error as ActivityAuthorizationError {
710
528
  switch error {
711
- case .denied:
712
- // User disabled Live Activities in Settings
713
- break
714
- case .globalMaximumExceeded:
715
- // Too many Live Activities across all apps
716
- break
717
- case .targetMaximumExceeded:
718
- // Too many Live Activities for this app
719
- break
720
- default:
721
- break
529
+ case .denied: showSettingsHint()
530
+ case .globalMaximumExceeded: showTryLater()
531
+ case .targetMaximumExceeded: endOldestActivity()
532
+ default: showGenericFailure()
722
533
  }
723
534
  }
724
535
  ```
725
536
 
726
- ## Widget Performance Best Practices
537
+ - `.denied`: the user switched Live Activities off in Settings.
538
+ - `.globalMaximumExceeded`: too many activities are running across all apps.
539
+ - `.targetMaximumExceeded`: this app already runs its maximum.
727
540
 
728
- ### Data Preparation
541
+ ## Keeping widgets fast
729
542
 
730
- Pre-compute display values in the timeline provider. Pass display-ready data
731
- through the entry.
543
+ - Do the math in the provider. The view should receive values ready to draw, not
544
+ raw records.
545
+ - Extensions have tight memory caps. Avoid big images in the view, large
546
+ datasets in entries and deep view trees.
547
+ - Store small, pre-resized thumbnails in the shared container and draw them with
548
+ `.resizable()` and `.aspectRatio(contentMode: .fill)` rather than loading
549
+ full-resolution files.
732
550
 
733
- ```swift
734
- // WRONG: Heavy computation in the widget view
735
- struct MyWidgetView: View {
736
- let entry: RawDataEntry
737
-
738
- var body: some View {
739
- let processed = HeavyProcessor.process(entry.rawData) // Slow
740
- Text(processed.summary)
741
- }
742
- }
743
-
744
- // CORRECT: Pre-compute in the provider
745
- func timeline(for configuration: Intent, in context: Context) async -> Timeline<ProcessedEntry> {
746
- let raw = await DataStore.shared.fetch()
747
- let processed = HeavyProcessor.process(raw)
748
- let entry = ProcessedEntry(date: .now, summary: processed.summary, value: processed.value)
749
- return Timeline(entries: [entry], policy: .atEnd)
750
- }
751
- ```
752
-
753
- ### Memory Constraints
754
-
755
- Widget extensions run with strict memory limits. Avoid:
756
- - Loading large images directly in the widget view
757
- - Storing large data sets in the entry
758
- - Creating complex view hierarchies
759
-
760
- ### Image Handling
551
+ Sharing through an App Group:
761
552
 
762
553
  ```swift
763
- // WRONG: Loading a full-resolution image
764
- Image(uiImage: UIImage(contentsOfFile: fullResPath)!)
554
+ // App side
555
+ let suite = UserDefaults(suiteName: "group.org.sample.garden")
556
+ suite?.set(62, forKey: "soilPercent")
557
+ WidgetCenter.shared.reloadTimelines(ofKind: "SoilMoisture")
765
558
 
766
- // CORRECT: Use a pre-resized thumbnail stored in the shared container
767
- Image(uiImage: UIImage(contentsOfFile: thumbnailPath)!)
768
- .resizable()
769
- .aspectRatio(contentMode: .fill)
559
+ // Provider side
560
+ let percent = UserDefaults(suiteName: "group.org.sample.garden")?.integer(forKey: "soilPercent") ?? 0
770
561
  ```
771
562
 
772
- ### Shared Data with App Groups
773
-
774
- ```swift
775
- // In the main app: write data
776
- let defaults = UserDefaults(suiteName: "group.com.example.myapp")
777
- defaults?.set(encodedData, forKey: "widgetData")
778
- WidgetCenter.shared.reloadTimelines(ofKind: "MyWidget")
779
-
780
- // In the widget provider: read data
781
- func timeline(for configuration: Intent, in context: Context) async -> Timeline<MyEntry> {
782
- let defaults = UserDefaults(suiteName: "group.com.example.myapp")
783
- let data = defaults?.data(forKey: "widgetData")
784
- // Decode and build entry
785
- }
786
- ```
787
-
788
- For larger datasets, use a shared SQLite database or Core Data store in the
789
- App Group container.
563
+ For bigger data, keep a SQLite or Core Data store inside the App Group container.
790
564
 
791
565
  ## Xcode Setup
792
566
 
793
- ### Adding a Widget Extension Target
794
-
795
- 1. File > New > Target > Widget Extension.
796
- 2. Name the extension (e.g., "MyAppWidgets").
797
- 3. Select "Include Configuration App Intent" for configurable widgets.
798
- 4. Select "Include Live Activity" if building Live Activities.
799
-
800
- ### Entitlements
801
-
802
- | Entitlement | Purpose |
567
+ - New target options: tick "Include Configuration App Intent" for a configurable
568
+ widget and "Include Live Activity" to get a Live Activity scaffold.
569
+ - Entitlements: `com.apple.security.application-groups` to share data between
570
+ targets, Push Notifications (`aps-environment`) when Live Activities update by push.
571
+ - Add the App Group to both targets with the same identifier, such as
572
+ `group.org.sample.garden`, then read it through `UserDefaults(suiteName:)` or
573
+ `FileManager.default.containerURL(forSecurityApplicationGroupIdentifier:)`.
574
+ - Debug by running the widget extension scheme, picking the "Widget" destination,
575
+ or using the canvas preview.
576
+
577
+ | Error | Fix |
803
578
  |---|---|
804
- | App Groups (`com.apple.security.application-groups`) | Share data between app and widget |
805
- | Push Notifications (`aps-environment`) | Required for push-based Live Activity updates |
806
-
807
- ### App Groups Configuration
808
-
809
- 1. Enable "App Groups" capability on both the main app target and the widget
810
- extension target.
811
- 2. Create a shared group identifier (e.g., `group.com.example.myapp`).
812
- 3. Use `UserDefaults(suiteName:)` or `FileManager.containerURL(forSecurityApplicationGroupIdentifier:)`
813
- for shared storage.
814
-
815
- ### Build Schemes
816
-
817
- - Use the widget extension scheme to debug widget rendering.
818
- - Select "Widget" as the run destination to launch the widget directly.
819
- - Use "Preview" in Xcode canvas for rapid iteration.
579
+ | "Widget extension must include at least one widget" | Put `@main` on the `WidgetBundle` |
580
+ | "No such module 'WidgetKit'" | Link WidgetKit and SwiftUI in the extension target |
581
+ | `ActivityKit.ActivityAuthorizationError error 3` | The `NSSupportsLiveActivities = YES` key belongs in the main app target's Info.plist; setting it on the extension does nothing |
820
582
 
821
- ### Common Xcode Issues
583
+ ## Smart Stack relevance
822
584
 
823
- ```text
824
- // ERROR: "Widget extension must include at least one widget"
825
- // FIX: Ensure @main is on the WidgetBundle, not a widget struct.
826
-
827
- // ERROR: "No such module 'WidgetKit'"
828
- // FIX: Ensure the widget extension target links WidgetKit and SwiftUI frameworks.
829
-
830
- // ERROR: "The operation couldn't be completed. (ActivityKit.ActivityAuthorizationError error 3.)"
831
- // FIX: Add NSSupportsLiveActivities = YES to the HOST APP's Info.plist (not the extension).
832
- ```
833
-
834
- ## Widget Relevance and Smart Stacks
835
-
836
- ### TimelineEntryRelevance
837
-
838
- Score entries to surface widgets in Smart Stacks when relevant:
585
+ An entry can tell Smart Stacks how important it is and for how long:
839
586
 
840
587
  ```swift
841
- struct GameEntry: TimelineEntry {
842
- var date: Date
843
- var score: String
844
- var isLive: Bool
588
+ struct MatchEntry: TimelineEntry {
589
+ let date: Date
590
+ let score: String
591
+ let isLive: Bool
845
592
 
846
593
  var relevance: TimelineEntryRelevance? {
847
- isLive ? TimelineEntryRelevance(score: 100, duration: 3600) : nil
594
+ isLive ? TimelineEntryRelevance(score: 90, duration: 2 * 3600) : nil
848
595
  }
849
596
  }
850
597
  ```
851
598
 
852
- Higher scores make the widget more likely to surface. The `duration` specifies
853
- how long the relevance lasts.
599
+ A higher score makes the stack more likely to rotate the widget up; `duration`
600
+ says how long the score holds.
854
601
 
855
- ### WidgetRelevance (AppIntentTimelineProvider)
602
+ Configurable widgets can also report relevance from the provider (iOS 18+). The
603
+ method returns a `WidgetRelevance` built from an array of
604
+ `WidgetRelevanceAttribute` values, each pairing one intent configuration with a
605
+ RelevanceKit context such as a date window. `.date(interval:kind:)` below needs
606
+ iOS 26; on iOS 18 the date window is `.date(from:to:)`:
856
607
 
857
608
  ```swift
858
- func relevance() async -> WidgetRelevance<SelectCategoryIntent> {
859
- let topCategory = await DataStore.shared.mostActiveCategory()
860
- let intent = SelectCategoryIntent(category: topCategory)
861
- return WidgetRelevance(intent, score: 80)
609
+ import RelevanceKit
610
+
611
+ func relevance() async -> WidgetRelevance<PickTeamIntent> {
612
+ let fixtures = await FixtureStore.shared.upcoming()
613
+ let attributes = fixtures.map { fixture in
614
+ WidgetRelevanceAttribute(
615
+ configuration: PickTeamIntent(team: fixture.team),
616
+ context: .date(interval: fixture.window, kind: .scheduled)
617
+ )
618
+ }
619
+ return WidgetRelevance(attributes)
862
620
  }
863
621
  ```
864
622
 
865
- ## ActivityState Lifecycle
623
+ `.date(interval:kind:)` is the iOS 26 form; on iOS 18 use `.date(from:to:)`,
624
+ which iOS 26 deprecates.
866
625
 
867
- Track the full lifecycle of a Live Activity:
626
+ ## ActivityState Lifecycle
868
627
 
869
628
  ```swift
870
629
  Task {
871
- for await state in activity.activityStateUpdates {
872
- switch state {
873
- case .active:
874
- // Activity is running and visible
875
- break
876
- case .pending:
877
- // Requested but not yet displayed (iOS 26+)
878
- break
879
- case .stale:
880
- // Content is outdated; update or end
881
- break
882
- case .ended:
883
- // Ended but may still be visible on Lock Screen
884
- break
885
- case .dismissed:
886
- // Fully removed from UI; clean up resources
887
- break
888
- @unknown default:
889
- break
630
+ for await phase in activity.activityStateUpdates {
631
+ switch phase {
632
+ case .active: break
633
+ case .pending: showQueuedBadge()
634
+ case .stale: await refreshOrEnd(activity)
635
+ case .ended: markFinished()
636
+ case .dismissed: cleanUp(activity.id)
637
+ @unknown default: break
890
638
  }
891
639
  }
892
640
  }
893
641
  ```
894
642
 
895
- ## ActivityStyle
643
+ | State | Meaning |
644
+ |---|---|
645
+ | `.active` | Running and visible |
646
+ | `.pending` | Requested but not on screen yet (iOS 26+) |
647
+ | `.stale` | Content is out of date; update it or end it |
648
+ | `.ended` | Finished, but may still sit on the Lock Screen |
649
+ | `.dismissed` | Gone from screen; release resources |
896
650
 
897
- Control Live Activity persistence behavior (iOS 18+):
651
+ ## ActivityStyle
898
652
 
899
653
  ```swift
900
- // Standard: persists until explicitly ended
901
- let activity = try Activity.request(
902
- attributes: attributes,
903
- content: content,
904
- pushType: .token,
905
- style: .standard
906
- )
907
-
908
- // Transient: automatically dismissed after a period
909
- let activity = try Activity.request(
910
- attributes: attributes,
911
- content: content,
912
- pushType: .token,
913
- style: .transient
914
- )
654
+ _ = try Activity.request(attributes: goalAlert, content: snapshot, pushType: nil, style: .transient)
915
655
  ```
916
656
 
917
- Use `.transient` for short-lived notifications like sports scores or transit
918
- arrivals that do not need persistent display.
657
+ `.standard` stays until you end it. `.transient` (iOS 18+) is removed by the
658
+ system after a while; use it for short bursts such as a goal alert or a bus
659
+ arriving.
919
660
 
920
661
  ## Dismissal Policies
921
662
 
922
- Control when an ended Live Activity disappears from the Lock Screen:
923
-
924
- ```swift
925
- // System-determined timing (default)
926
- await activity.end(finalContent, dismissalPolicy: .default)
927
-
928
- // Remove immediately
929
- await activity.end(finalContent, dismissalPolicy: .immediate)
930
-
931
- // Remove after a specific date (max 4 hours)
932
- let removalDate = Date().addingTimeInterval(3600)
933
- await activity.end(finalContent, dismissalPolicy: .after(removalDate))
934
- ```
935
-
936
- ## Querying Active Widgets and Activities
663
+ | Policy | Effect |
664
+ |---|---|
665
+ | `.default` | The system chooses when to remove it |
666
+ | `.immediate` | Removed right away |
667
+ | `.after(date)` | Removed at the given date, at most 4 hours after ending |
937
668
 
938
- ### Current Widget Configurations
669
+ ## Finding which widgets and activities are live
939
670
 
940
671
  ```swift
941
- let widgets = try await WidgetCenter.shared.currentConfigurations()
942
- for widget in widgets {
943
- print("Kind: \(widget.kind), Family: \(widget.family)")
672
+ let configs = try await WidgetCenter.shared.currentConfigurations()
673
+ for info in configs {
674
+ logger.debug("\(info.kind) \(String(describing: info.family))")
944
675
  }
945
- ```
946
-
947
- ### Current Live Activities
948
676
 
949
- ```swift
950
- let activities = Activity<DeliveryAttributes>.activities
951
- for activity in activities {
952
- print("ID: \(activity.id), State: \(activity.activityState)")
677
+ for activity in Activity<FerryAttributes>.activities {
678
+ logger.debug("\(activity.id) \(String(describing: activity.activityState))")
953
679
  }
954
- ```
955
-
956
- ### Observing New Activities
957
680
 
958
- ```swift
959
681
  Task {
960
- for await activity in Activity<DeliveryAttributes>.activityUpdates {
961
- print("New activity started: \(activity.id)")
682
+ for await started in Activity<FerryAttributes>.activityUpdates {
683
+ observe(started)
962
684
  }
963
685
  }
964
686
  ```
965
687
 
966
- ## Design Patterns
688
+ `currentConfigurations()` lists placed widgets with their `kind` and `family`.
689
+ `Activity<T>.activities` returns running activities, and `activityUpdates`
690
+ reports ones started later, including by push. `logger` is an `os.Logger`.
967
691
 
968
- ### Prefer Gauge for Value Indicators
692
+ ## Design Patterns
969
693
 
970
- Use `Gauge` (iOS 16+) instead of manual `Circle` or `Path` arcs to show a value
971
- within a range. The system handles styling, accessibility, and rendering-mode
972
- adaptation automatically.
694
+ ### Gauge (iOS 16+)
973
695
 
974
- - `.accessoryCircular` - open ring with center value label, matches the system
975
- complication style. Use for `accessoryCircular` Lock Screen widgets.
976
- - `.linearCapacity` - horizontal bar that fills leading to trailing. Use for
977
- home screen widgets when a capacity bar fits.
696
+ `.accessoryCircular` draws an open ring with the value in the middle, the same
697
+ look as system complications. `.linearCapacity` fills from leading to trailing.
978
698
 
979
699
  ```swift
980
- // accessoryCircular Lock Screen widget
981
- struct StepsCircularView: View {
982
- let entry: StepsEntry
983
-
984
- var body: some View {
985
- Gauge(value: Double(entry.stepCount), in: 0...10000) {
986
- Image(systemName: "figure.walk")
987
- } currentValueLabel: {
988
- Text("\(entry.stepCount)")
989
- }
990
- .gaugeStyle(.accessoryCircular)
991
- }
700
+ Gauge(value: Double(entry.steps), in: 0...10000) {
701
+ Image(systemName: "figure.walk")
702
+ } currentValueLabel: {
703
+ Text(entry.steps, format: .number.notation(.compactName))
992
704
  }
705
+ .gaugeStyle(.accessoryCircular)
993
706
 
994
- // Home screen capacity bar
995
- Gauge(value: storageUsed, in: 0...storageTotal) {
996
- Text("Storage")
707
+ Gauge(value: entry.usedBytes, in: 0...entry.totalBytes) {
708
+ Text("Backup")
997
709
  } currentValueLabel: {
998
- Text(storageUsed, format: .byteCount(style: .file))
710
+ Text(Int64(entry.usedBytes).formatted(.byteCount(style: .file)))
999
711
  }
1000
712
  .gaugeStyle(.linearCapacity)
1001
713
  ```
1002
714
 
1003
- ### Use containerBackground for Widget Backgrounds
1004
-
1005
- `.containerBackground(_:for: .widget)` (iOS 17+) is the designated way to set
1006
- widget backgrounds. Replaces older padding and background patterns. The system
1007
- uses this placement to correctly render backgrounds across all widget surfaces.
715
+ ### Container background
1008
716
 
1009
717
  ```swift
1010
- struct OrderWidgetView: View {
1011
- let entry: OrderEntry
1012
-
1013
- var body: some View {
1014
- VStack(alignment: .leading) {
1015
- Text(entry.orderName).font(.headline)
1016
- Text(entry.status).foregroundStyle(.secondary)
1017
- }
1018
- .containerBackground(.fill.tertiary, for: .widget)
1019
- }
1020
- }
718
+ TideSummary(entry: entry)
719
+ .containerBackground(.fill.tertiary, for: .widget)
1021
720
  ```
1022
721
 
1023
- ### Use Canvas for Dense Visualizations
722
+ The system knows where the widget is placed and draws the background correctly
723
+ on every surface, including removing it where the surface wants none.
1024
724
 
1025
- Use `Canvas` for sparklines, mini bar charts, or heat maps inside widgets. The
1026
- lack of per-element accessibility is acceptable since the entire widget surface
1027
- is a single tap target.
725
+ ### Canvas sparkline
1028
726
 
1029
727
  ```swift
1030
- struct SparklineView: View {
728
+ struct Sparkline: View {
1031
729
  let values: [Double]
1032
730
 
1033
731
  var body: some View {
1034
732
  Canvas { context, size in
1035
- guard values.count > 1 else { return }
1036
- let maxVal = values.max() ?? 1
1037
- let step = size.width / CGFloat(values.count - 1)
733
+ guard values.count >= 2, let low = values.min(), let high = values.max() else { return }
734
+ let span = max(high - low, .ulpOfOne)
735
+ let spacing = size.width / Double(values.count - 1)
1038
736
  var path = Path()
1039
- for (i, value) in values.enumerated() {
1040
- let x = step * CGFloat(i)
1041
- let y = size.height * (1 - value / maxVal)
1042
- if i == 0 { path.move(to: CGPoint(x: x, y: y)) }
1043
- else { path.addLine(to: CGPoint(x: x, y: y)) }
737
+ for (index, value) in values.enumerated() {
738
+ let point = CGPoint(
739
+ x: Double(index) * spacing,
740
+ y: size.height * (1 - CGFloat((value - low) / span))
741
+ )
742
+ index == 0 ? path.move(to: point) : path.addLine(to: point)
1044
743
  }
1045
- context.stroke(path, with: .color(.blue), lineWidth: 2)
744
+ context.stroke(path, with: .color(.green), lineWidth: 1.5)
1046
745
  }
1047
746
  }
1048
747
  }
1049
748
  ```
1050
749
 
1051
- ### Match Timeline Refresh to Data Granularity
1052
-
1053
- Apple budgets
1054
- [40-70 refreshes per day](https://sosumi.ai/documentation/widgetkit/keeping-a-widget-up-to-date)
1055
- for frequently viewed widgets, with entries at least 5 minutes apart. Align
1056
- reload cadence to how often the underlying data actually changes.
1057
-
1058
- - Generate entries for as many future dates as possible to reduce reload requests.
1059
- - Use `.after(date)` when data updates on a known schedule (market hours, transit).
1060
- - Use `.never` when data only changes from user action.
1061
- - Use `Text(timerInterval:countsDown:)` for live countdowns instead of burning
1062
- timeline entries on every tick.
1063
-
1064
- ## Apple Documentation Links
1065
-
1066
- - [WidgetKit](https://sosumi.ai/documentation/widgetkit)
1067
- - [ActivityKit](https://sosumi.ai/documentation/activitykit)
1068
- - [TimelineProvider](https://sosumi.ai/documentation/widgetkit/timelineprovider)
1069
- - [AppIntentTimelineProvider](https://sosumi.ai/documentation/widgetkit/appintenttimelineprovider)
1070
- - [ActivityAttributes](https://sosumi.ai/documentation/activitykit/activityattributes)
1071
- - [ActivityConfiguration](https://sosumi.ai/documentation/widgetkit/activityconfiguration)
1072
- - [DynamicIsland](https://sosumi.ai/documentation/widgetkit/dynamicisland)
1073
- - [ControlWidgetButton](https://sosumi.ai/documentation/widgetkit/controlwidgetbutton)
1074
- - [ControlWidgetToggle](https://sosumi.ai/documentation/widgetkit/controlwidgettoggle)
1075
- - [Keeping a widget up to date](https://sosumi.ai/documentation/widgetkit/keeping-a-widget-up-to-date)
1076
- - [Adding StandBy and CarPlay support](https://sosumi.ai/documentation/widgetkit/adding-standby-and-carplay-support-to-your-widget)
1077
- - [Optimizing for accented rendering and Liquid Glass](https://sosumi.ai/documentation/widgetkit/optimizing-your-widget-for-accented-rendering-mode-and-liquid-glass)
750
+ ### Refresh budget in practice
751
+
752
+ - Widgets people look at often get about 40 to 70 refreshes a day, with entries
753
+ no closer than 5 minutes.
754
+ - Put as many future entries into each timeline as you can predict.
755
+ - Use `.after(date)` for scheduled data and `.never` for data only the user changes.
756
+ - Let `Text(timerInterval:countsDown:)` or a timer text style count down instead
757
+ of writing an entry per second.
758
+
759
+ ## Further reading from Apple
760
+
761
+ - [WidgetKit](https://developer.apple.com/documentation/widgetkit)
762
+ - [ActivityKit](https://developer.apple.com/documentation/activitykit)
763
+ - [TimelineProvider](https://developer.apple.com/documentation/widgetkit/timelineprovider)
764
+ - [AppIntentTimelineProvider](https://developer.apple.com/documentation/widgetkit/appintenttimelineprovider)
765
+ - [ActivityAttributes](https://developer.apple.com/documentation/activitykit/activityattributes)
766
+ - [ActivityConfiguration](https://developer.apple.com/documentation/widgetkit/activityconfiguration)
767
+ - [DynamicIsland](https://developer.apple.com/documentation/widgetkit/dynamicisland)
768
+ - [ControlWidgetButton](https://developer.apple.com/documentation/widgetkit/controlwidgetbutton)
769
+ - [ControlWidgetToggle](https://developer.apple.com/documentation/widgetkit/controlwidgettoggle)
770
+ - [Keeping a widget up to date](https://developer.apple.com/documentation/widgetkit/keeping-a-widget-up-to-date)
771
+ - [Adding StandBy and CarPlay support to your widget](https://developer.apple.com/documentation/widgetkit/adding-standby-and-carplay-support-to-your-widget)
772
+ - [Optimizing your widget for accented rendering mode and Liquid Glass](https://developer.apple.com/documentation/widgetkit/optimizing-your-widget-for-accented-rendering-mode-and-liquid-glass)