@mmerterden/multi-agent-pipeline 20.6.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 (326) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/LICENSE +0 -10
  3. package/docs/facts.json +1 -1
  4. package/manifest.json +328 -329
  5. package/package.json +2 -2
  6. package/pipeline/multi-agent-refs/features/design-conformance.md +62 -64
  7. package/pipeline/scripts/_notices.mjs +12 -1
  8. package/pipeline/scripts/gen-skills-index.mjs +1 -1
  9. package/pipeline/skills/.skill-manifest.json +106 -106
  10. package/pipeline/skills/shared/README.md +71 -71
  11. package/pipeline/skills/shared/external/agent-introspection-debugging/SKILL.md +1 -0
  12. package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
  13. package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
  14. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
  15. package/pipeline/skills/shared/external/android-architecture/SKILL.md +2 -0
  16. package/pipeline/skills/shared/external/android-performance/SKILL.md +2 -0
  17. package/pipeline/skills/shared/external/android-security/SKILL.md +2 -0
  18. package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
  19. package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
  20. package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
  21. package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
  22. package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
  23. package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
  24. package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
  25. package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
  26. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
  27. package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
  28. package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
  29. package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
  30. package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
  31. package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
  32. package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
  33. package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
  34. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
  35. package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
  36. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
  37. package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
  38. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
  39. package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
  40. package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
  41. package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
  42. package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
  43. package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
  44. package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
  45. package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
  46. package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
  47. package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
  48. package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
  49. package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
  50. package/pipeline/skills/shared/external/backlog/BACKLOG.md +1 -1
  51. package/pipeline/skills/shared/external/backlog/SKILL.md +56 -33
  52. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
  53. package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
  54. package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
  55. package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +1 -0
  56. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
  57. package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
  58. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
  59. package/pipeline/skills/shared/external/compose-components/SKILL.md +2 -0
  60. package/pipeline/skills/shared/external/compose-navigation/SKILL.md +3 -2
  61. package/pipeline/skills/shared/external/compose-testing/SKILL.md +2 -0
  62. package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
  63. package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
  64. package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
  65. package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
  66. package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
  67. package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
  68. package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
  69. package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
  70. package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
  71. package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
  72. package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
  73. package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
  74. package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
  75. package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
  76. package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
  77. package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
  78. package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
  79. package/pipeline/skills/shared/external/council/SKILL.md +1 -0
  80. package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
  81. package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
  82. package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
  83. package/pipeline/skills/shared/external/css-modern/SKILL.md +1 -0
  84. package/pipeline/skills/shared/external/database-patterns/SKILL.md +1 -0
  85. package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
  86. package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
  87. package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
  88. package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
  89. package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
  90. package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
  91. package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
  92. package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
  93. package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
  94. package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
  95. package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
  96. package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
  97. package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
  98. package/pipeline/skills/shared/external/evidence-github/SKILL.md +2 -0
  99. package/pipeline/skills/shared/external/evidence-registry/SKILL.md +2 -0
  100. package/pipeline/skills/shared/external/gradle-kotlin-dsl/SKILL.md +2 -0
  101. package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
  102. package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
  103. package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
  104. package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
  105. package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
  106. package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
  107. package/pipeline/skills/shared/external/html-semantic/SKILL.md +1 -0
  108. package/pipeline/skills/shared/external/humanizer/SKILL.md +1 -0
  109. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
  110. package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
  111. package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
  112. package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
  113. package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
  114. package/pipeline/skills/shared/external/ios-coding-standard/SKILL.md +1 -0
  115. package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
  116. package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
  117. package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
  118. package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
  119. package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +1 -0
  120. package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
  121. package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
  122. package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
  123. package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
  124. package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
  125. package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
  126. package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
  127. package/pipeline/skills/shared/external/ios-security/SKILL.md +2 -0
  128. package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
  129. package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
  130. package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
  131. package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
  132. package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
  133. package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
  134. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +91 -283
  135. package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +119 -151
  136. package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +60 -90
  137. package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +119 -156
  138. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +726 -787
  139. package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +253 -288
  140. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +243 -304
  141. package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +88 -104
  142. package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +181 -235
  143. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +198 -263
  144. package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +461 -466
  145. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +145 -151
  146. package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +123 -141
  147. package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +146 -157
  148. package/pipeline/skills/shared/external/localization-reuse-map/scripts/snapshot-resources.sh +22 -19
  149. package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +156 -140
  150. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
  151. package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
  152. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
  153. package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
  154. package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
  155. package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
  156. package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
  157. package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
  158. package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
  159. package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
  160. package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
  161. package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
  162. package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
  163. package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +1 -0
  164. package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
  165. package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
  166. package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
  167. package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
  168. package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
  169. package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
  170. package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
  171. package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
  172. package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
  173. package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
  174. package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
  175. package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
  176. package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
  177. package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
  178. package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
  179. package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
  180. package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
  181. package/pipeline/skills/shared/external/play-store-review/SKILL.md +2 -0
  182. package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
  183. package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
  184. package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
  185. package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
  186. package/pipeline/skills/shared/external/python-patterns/SKILL.md +1 -0
  187. package/pipeline/skills/shared/external/react-best-practices/SKILL.md +1 -0
  188. package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
  189. package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
  190. package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
  191. package/pipeline/skills/shared/external/rest-api-design/SKILL.md +1 -0
  192. package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +2 -0
  193. package/pipeline/skills/shared/external/room-database/SKILL.md +2 -0
  194. package/pipeline/skills/shared/external/search-first/SKILL.md +1 -0
  195. package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
  196. package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
  197. package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
  198. package/pipeline/skills/shared/external/signal-community/SKILL.md +2 -0
  199. package/pipeline/skills/shared/external/skill-creator/SKILL.md +80 -41
  200. package/pipeline/skills/shared/external/skill-creator/audit.md +63 -59
  201. package/pipeline/skills/shared/external/skill-creator/checklist.md +28 -20
  202. package/pipeline/skills/shared/external/skill-creator/examples.md +40 -40
  203. package/pipeline/skills/shared/external/skill-creator/label-check.md +48 -36
  204. package/pipeline/skills/shared/external/skill-creator/scripts/audit-panel.js +91 -100
  205. package/pipeline/skills/shared/external/skill-creator/template.md +51 -39
  206. package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
  207. package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
  208. package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
  209. package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
  210. package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
  211. package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
  212. package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
  213. package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
  214. package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
  215. package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
  216. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
  217. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
  218. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
  219. package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
  220. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
  221. package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
  222. package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
  223. package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
  224. package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
  225. package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
  226. package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
  227. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
  228. package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
  229. package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
  230. package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
  231. package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
  232. package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
  233. package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
  234. package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
  235. package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
  236. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
  237. package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
  238. package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
  239. package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
  240. package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
  241. package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
  242. package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
  243. package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
  244. package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
  245. package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
  246. package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
  247. package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
  248. package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
  249. package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
  250. package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
  251. package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
  252. package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
  253. package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
  254. package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
  255. package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
  256. package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
  257. package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
  258. package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
  259. package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
  260. package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
  261. package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
  262. package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
  263. package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
  264. package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
  265. package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
  266. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
  267. package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
  268. package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
  269. package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
  270. package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
  271. package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
  272. package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
  273. package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
  274. package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
  275. package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
  276. package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
  277. package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
  278. package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
  279. package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
  280. package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
  281. package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
  282. package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
  283. package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
  284. package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
  285. package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
  286. package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
  287. package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
  288. package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
  289. package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
  290. package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
  291. package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
  292. package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
  293. package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
  294. package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
  295. package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
  296. package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
  297. package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
  298. package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
  299. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
  300. package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
  301. package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
  302. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
  303. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
  304. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
  305. package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
  306. package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
  307. package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
  308. package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
  309. package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
  310. package/pipeline/skills/shared/external/tailwind-css/SKILL.md +1 -0
  311. package/pipeline/skills/shared/external/testing-backend/SKILL.md +1 -0
  312. package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
  313. package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
  314. package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +1 -0
  315. package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
  316. package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
  317. package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
  318. package/pipeline/skills/shared/external/vue-composition/SKILL.md +1 -0
  319. package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
  320. package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
  321. package/pipeline/skills/shared/external/web-accessibility/SKILL.md +1 -0
  322. package/pipeline/skills/shared/external/web-performance/SKILL.md +1 -0
  323. package/pipeline/skills/shared/external/web-testing/SKILL.md +1 -0
  324. package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
  325. package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
  326. package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
@@ -1,122 +1,107 @@
1
- # TipKit Patterns Reference
1
+ # TipKit Patterns
2
2
 
3
- Complete implementation patterns for TipKit including custom styles, event-based
4
- rules, tip groups, testing strategies, onboarding flows, and SwiftUI previews.
5
- All examples target iOS 17+ with Swift 6.3 conventions.
3
+ Longer worked patterns for the `tipkit` skill. Every example targets iOS 17 or
4
+ later and follows Swift 6.3 conventions; a heading says so when a newer
5
+ release is needed.
6
6
 
7
7
  ## Contents
8
8
 
9
- - [Complete Tip with Rules and Events](#complete-tip-with-rules-and-events)
10
- - [TipView and popoverTip Placement](#tipview-and-popovertip-placement)
11
- - [Event-Based Rule with Donation Counting](#event-based-rule-with-donation-counting)
9
+ - [A tip with both a parameter and an event rule](#a-tip-with-both-a-parameter-and-an-event-rule)
10
+ - [Placing TipView and popoverTip](#placing-tipview-and-popovertip)
11
+ - [Counting a manual action to suggest a shortcut](#counting-a-manual-action-to-suggest-a-shortcut)
12
+ - [Events that carry a value](#events-that-carry-a-value)
12
13
  - [Custom TipViewStyle](#custom-tipviewstyle)
13
- - [TipGroup Sequencing](#tipgroup-sequencing)
14
- - [Testing Strategies](#testing-strategies)
15
- - [Tip with Action Buttons](#tip-with-action-buttons)
16
- - [Integration with Onboarding Flow](#integration-with-onboarding-flow)
17
- - [Full App Integration Example](#full-app-integration-example)
14
+ - [Sequencing with TipGroup (iOS 18)](#sequencing-with-tipgroup-ios-18)
15
+ - [Testing](#testing)
16
+ - [Actions that open a feature](#actions-that-open-a-feature)
17
+ - [Working alongside an onboarding flow](#working-alongside-an-onboarding-flow)
18
+ - [A full app](#a-full-app)
18
19
 
19
- ## Complete Tip with Rules and Events
20
+ ## A tip with both a parameter and an event rule
20
21
 
21
- A full-featured tip combining parameter-based and event-based rules. The tip
22
- appears only after the user has logged in and opened the app at least three
23
- times, ensuring they are familiar with the basics before seeing advanced
24
- feature discovery.
22
+ This tip advertises bulk export. It waits until the person is signed in and
23
+ has exported single recipes a few times, so the advanced feature is offered
24
+ only to someone who already knows the basic one.
25
25
 
26
26
  ```swift
27
27
  import TipKit
28
28
 
29
- struct AdvancedSearchTip: Tip {
30
- // Parameter rule: user must be logged in
29
+ struct BulkExportTip: Tip {
31
30
  @Parameter
32
- static var isLoggedIn: Bool = false
31
+ static var isSignedIn: Bool = false
33
32
 
34
- // Event rule: user must have performed searches
35
- static let searchPerformed = Tips.Event(id: "searchPerformed")
33
+ static let recipeExported = Tips.Event(id: "recipeExported")
36
34
 
37
- var title: Text {
38
- Text("Try Advanced Search")
39
- }
35
+ var title: Text { Text("Export a whole cookbook") }
40
36
 
41
37
  var message: Text? {
42
- Text("Filter results by date, category, and location for faster discovery.")
38
+ Text("Select several recipes and share them as one file.")
43
39
  }
44
40
 
45
- var image: Image? {
46
- Image(systemName: "magnifyingglass")
47
- }
41
+ var image: Image? { Image(systemName: "tray.and.arrow.up") }
48
42
 
49
- // All rules must pass before the tip becomes eligible
50
- var rules: [Rule] {
51
- #Rule(Self.$isLoggedIn) { $0 == true }
52
- #Rule(Self.searchPerformed) { $0.donations.count >= 3 }
43
+ var options: [TipOption] {
44
+ Tips.MaxDisplayCount(5)
53
45
  }
54
46
 
55
- var options: [TipOption] {
56
- MaxDisplayCount(5)
47
+ var rules: [Rule] {
48
+ #Rule(Self.$isSignedIn) { $0 == true }
49
+ #Rule(Self.recipeExported) { $0.donations.count >= 3 }
57
50
  }
58
51
  }
59
52
  ```
60
53
 
61
- ### Donating to Events
54
+ ### Where to donate
62
55
 
63
- Place event donations at the point where the user action occurs. Each
64
- donation increments the internal counter that rules evaluate against.
56
+ Donate at the exact place the action completes, for example in the export
57
+ callback. Every donation raises the count the rule checks.
65
58
 
66
59
  ```swift
67
- struct SearchView: View {
68
- @State private var query = ""
69
-
70
- var body: some View {
71
- SearchBar(text: $query, onSubmit: {
72
- performSearch(query)
73
- // Donate each time the user searches
74
- AdvancedSearchTip.searchPerformed.donate()
75
- })
76
- }
60
+ func exportRecipe(_ recipe: Recipe) async throws {
61
+ try await exporter.write(recipe)
62
+ await BulkExportTip.recipeExported.donate()
77
63
  }
78
64
  ```
79
65
 
80
- ### Setting Parameters
66
+ ### Where to set parameters
81
67
 
82
- Set parameter values when the relevant app state changes. Parameters persist
83
- across launches via the TipKit datastore.
68
+ Set a parameter when the underlying state changes, such as in the sign-in
69
+ success path. The value is stored in the TipKit datastore, so it survives
70
+ relaunches.
84
71
 
85
72
  ```swift
86
- func handleLoginSuccess() {
87
- AdvancedSearchTip.isLoggedIn = true
73
+ func didFinishSignIn() {
74
+ BulkExportTip.isSignedIn = true
88
75
  }
89
76
  ```
90
77
 
91
- ## TipView and popoverTip Placement
78
+ ## Placing TipView and popoverTip
92
79
 
93
- ### Inline TipView in a List
80
+ ### A tip as a list row, the same tip as a toolbar popover
94
81
 
95
- Place a `TipView` as a list row for contextual inline discovery. The tip
96
- appears as part of the list content and animates away when dismissed or
97
- invalidated.
82
+ An inline `TipView` can be a row of a `List`; it animates away once dismissed
83
+ or invalidated. The toolbar button below also carries the tip as a popover and
84
+ invalidates it when tapped.
98
85
 
99
86
  ```swift
100
- struct ItemListView: View {
101
- let filterTip = FilterTip()
102
- @State private var items: [Item] = []
87
+ struct PantryListView: View {
88
+ let filterTip = PantryFilterTip()
89
+ @State private var isFilterSheetShown = false
103
90
 
104
91
  var body: some View {
105
92
  List {
106
- TipView(filterTip)
107
-
108
- ForEach(items) { item in
109
- ItemRow(item: item)
93
+ Section {
94
+ TipView(filterTip)
95
+ }
96
+ ForEach(pantryItems) { item in
97
+ PantryRow(item: item)
110
98
  }
111
99
  }
112
- .navigationTitle("Items")
113
100
  .toolbar {
114
101
  ToolbarItem(placement: .primaryAction) {
115
- Button {
116
- showFilters()
102
+ Button("Filter", systemImage: "slider.horizontal.3") {
117
103
  filterTip.invalidate(reason: .actionPerformed)
118
- } label: {
119
- Image(systemName: "line.3.horizontal.decrease.circle")
104
+ isFilterSheetShown = true
120
105
  }
121
106
  .popoverTip(filterTip, arrowEdge: .top)
122
107
  }
@@ -125,658 +110,535 @@ struct ItemListView: View {
125
110
  }
126
111
  ```
127
112
 
128
- ### Popover on Navigation Bar Button
113
+ ### Pointing at a navigation bar button
129
114
 
130
- Attach a popover tip to a toolbar button. The popover arrow points to the
131
- button, drawing the user's attention to the exact control.
115
+ A popover on a bar button aims its arrow at exactly that control.
132
116
 
133
117
  ```swift
134
- struct EditorView: View {
135
- let undoTip = UndoShortcutTip()
136
-
137
- var body: some View {
138
- TextEditor(text: $text)
139
- .toolbar {
140
- ToolbarItem(placement: .primaryAction) {
141
- Button("Undo", systemImage: "arrow.uturn.backward") {
142
- undoLastAction()
143
- undoTip.invalidate(reason: .actionPerformed)
144
- }
145
- .popoverTip(undoTip, arrowEdge: .top)
146
- }
147
- }
148
- }
118
+ Button("Revert", systemImage: "arrow.uturn.backward") {
119
+ revertLastChange()
149
120
  }
121
+ .popoverTip(revertTip, arrowEdge: .top)
150
122
  ```
151
123
 
152
- ### Popover on Tab Bar Item
124
+ ### Introducing a new tab (iOS 18)
153
125
 
154
- Use `popoverTip` on a `Tab` label view inside a `TabView` to highlight a
155
- new tab.
126
+ TipKit has no `popoverTip` for `Tab`: the modifier is declared on `View`, and a
127
+ `Tab` is `TabContent`, so `.popoverTip` on it does not compile. Put the tip
128
+ inside the new tab's content instead.
156
129
 
157
130
  ```swift
158
- struct MainTabView: View {
159
- let newTabTip = NewFeatureTabTip()
160
-
161
- var body: some View {
162
- TabView {
163
- Tab("Home", systemImage: "house") {
164
- HomeView()
165
- }
166
-
167
- Tab("Discover", systemImage: "sparkles") {
168
- DiscoverView()
169
- }
170
- .popoverTip(newTabTip)
131
+ TabView {
132
+ Tab("Recipes", systemImage: "book") {
133
+ RecipesView()
134
+ }
135
+ Tab("Meal Plan", systemImage: "calendar") {
136
+ VStack {
137
+ TipView(mealPlanTip)
138
+ MealPlanView()
171
139
  }
172
140
  }
173
141
  }
174
142
  ```
175
143
 
176
- ## Event-Based Rule with Donation Counting
144
+ ## Counting a manual action to suggest a shortcut
177
145
 
178
- Track how many times the user performs an action, then show a tip suggesting
179
- a more efficient alternative. This pattern is effective for progressive
180
- disclosure: let users learn the basic workflow first, then reveal shortcuts.
146
+ A progressive-disclosure pattern: count how often someone does something the
147
+ slow way, and after a threshold (five here) suggest the faster path.
181
148
 
182
149
  ```swift
183
- struct KeyboardShortcutTip: Tip {
184
- static let manualSaveEvent = Tips.Event(id: "manualSave")
185
-
186
- var title: Text {
187
- Text("Save Faster with Command-S")
188
- }
189
-
190
- var message: Text? {
191
- Text("Press Command-S instead of using the menu to save your work instantly.")
192
- }
150
+ struct SaveShortcutTip: Tip {
151
+ var title: Text { Text("Press Command-S to save") }
152
+ var message: Text? { Text("Works whenever a keyboard is attached.") }
193
153
 
194
- var image: Image? {
195
- Image(systemName: "keyboard")
196
- }
154
+ static let manualSave = Tips.Event(id: "manualSave")
197
155
 
198
156
  var rules: [Rule] {
199
- // Show after user has manually saved 5 times via button
200
- #Rule(Self.manualSaveEvent) { $0.donations.count >= 5 }
157
+ #Rule(Self.manualSave) { $0.donations.count >= 5 }
201
158
  }
202
159
  }
203
160
 
204
- struct DocumentView: View {
205
- let shortcutTip = KeyboardShortcutTip()
161
+ struct NoteEditorView: View {
162
+ @State private var noteText = ""
163
+ let keyboardHint = SaveShortcutTip()
206
164
 
207
165
  var body: some View {
208
- VStack {
209
- TipView(shortcutTip)
210
- DocumentEditor(document: $document)
166
+ VStack(spacing: 0) {
167
+ TipView(keyboardHint)
168
+ TextEditor(text: $noteText)
211
169
  }
212
170
  .toolbar {
213
- ToolbarItem {
214
- Button("Save") {
215
- saveDocument()
216
- KeyboardShortcutTip.manualSaveEvent.donate()
217
- }
171
+ Button("Save") {
172
+ saveDraft()
173
+ Task { await SaveShortcutTip.manualSave.donate() }
218
174
  }
219
175
  }
220
176
  }
221
177
  }
222
178
  ```
223
179
 
224
- ### Event Donations with Associated Values
180
+ ## Events that carry a value
225
181
 
226
- Attach a `DonationValue` to event donations for richer rule evaluation.
227
- Use `Codable`-conforming types to provide context about each donation.
182
+ An event can record a value with every donation. Declare it as
183
+ `Tips.Event<Value>` where the value type is `Codable` and `Sendable`, filter
184
+ on the value in the rule, and pass the value to `donate`. Inside `#Rule`, give
185
+ `filter` its closure in parentheses: the macro rejects trailing-closure
186
+ syntax with "The filter function is not supported in this rule".
228
187
 
229
188
  ```swift
230
- struct DetailedTip: Tip {
231
- struct DonationInfo: Codable, Sendable {
232
- let category: String
233
- let timestamp: Date
234
- }
189
+ struct PlaybackInfo: Codable, Sendable {
190
+ let tier: String
191
+ }
192
+
193
+ struct OfflineDownloadTip: Tip {
194
+ static let episodePlayed = Tips.Event<PlaybackInfo>(id: "episodePlayed")
235
195
 
236
- static let itemViewed = Tips.Event<DonationInfo>(id: "itemViewed")
196
+ var title: Text { Text("Download for offline listening") }
237
197
 
238
198
  var rules: [Rule] {
239
- #Rule(Self.itemViewed) {
240
- $0.donations.filter {
241
- $0.category == "premium"
242
- }.count >= 3
199
+ #Rule(Self.episodePlayed) {
200
+ $0.donations.filter({ $0.tier == "premium" }).count >= 3
243
201
  }
244
202
  }
245
-
246
- var title: Text { Text("Unlock Premium Content") }
247
203
  }
248
204
 
249
- // Donate with associated value
250
- DetailedTip.itemViewed.donate(
251
- DetailedTip.DonationInfo(category: "premium", timestamp: .now)
252
- )
205
+ Task {
206
+ await OfflineDownloadTip.episodePlayed.donate(PlaybackInfo(tier: "premium"))
207
+ }
253
208
  ```
254
209
 
255
210
  ## Custom TipViewStyle
256
211
 
257
- Create a branded tip appearance that matches the app's design language.
258
- The `Configuration` provides access to the tip's title, message, image,
259
- and actions.
212
+ `Configuration` gives the style `title`, `message`, `image` and `actions`.
213
+
214
+ ### A branded card
260
215
 
261
216
  ```swift
262
- struct BrandedTipStyle: TipViewStyle {
217
+ struct BrandCardTipStyle: TipViewStyle {
263
218
  func makeBody(configuration: Configuration) -> some View {
264
- HStack(alignment: .top) {
265
- configuration.image?
266
- .font(.system(size: 24))
267
- .foregroundStyle(.white)
268
- .frame(width: 44, height: 44)
269
- .background(.blue.gradient, in: RoundedRectangle(cornerRadius: 10))
270
-
271
- VStack(alignment: .leading) {
272
- configuration.title
273
- .font(.headline)
274
-
275
- configuration.message?
276
- .font(.subheadline)
277
- .foregroundStyle(.secondary)
278
-
279
- if !configuration.actions.isEmpty {
280
- HStack {
281
- ForEach(configuration.actions) { action in
282
- Button(action: action.handler) {
283
- action.label
284
- .font(.subheadline.bold())
285
- }
286
- .buttonStyle(.bordered)
219
+ VStack(alignment: .leading, spacing: 12) {
220
+ HStack(alignment: .top, spacing: 12) {
221
+ configuration.image
222
+ .font(.title3)
223
+ .frame(width: 44, height: 44, alignment: .center)
224
+ .background(
225
+ LinearGradient(
226
+ colors: [.orange, .pink],
227
+ startPoint: .topLeading,
228
+ endPoint: .bottomTrailing
229
+ ),
230
+ in: RoundedRectangle(cornerRadius: 12)
231
+ )
232
+ .foregroundStyle(.white)
233
+ VStack(alignment: .leading, spacing: 4) {
234
+ configuration.title
235
+ .font(.title3.weight(.semibold))
236
+ configuration.message
237
+ .font(.callout)
238
+ .foregroundStyle(.gray)
239
+ }
240
+ }
241
+ if configuration.actions.isEmpty == false {
242
+ HStack(spacing: 8) {
243
+ ForEach(configuration.actions) { tipAction in
244
+ Button(action: tipAction.handler) {
245
+ tipAction.label()
287
246
  }
247
+ .buttonStyle(.bordered)
288
248
  }
289
- .padding(.top)
290
249
  }
291
250
  }
292
251
  }
293
252
  .padding()
294
- .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16))
253
+ .background(.thinMaterial, in: RoundedRectangle(cornerRadius: 18))
295
254
  }
296
255
  }
297
256
  ```
298
257
 
299
- ### Applying the Custom Style
300
-
301
- Apply the style to individual `TipView` instances or set it as the
302
- environment default.
258
+ Apply it to one `TipView`, or set it on a parent such as a `NavigationStack`
259
+ so it becomes the default for everything inside:
303
260
 
304
261
  ```swift
305
- // Per view
306
- TipView(myTip)
307
- .tipViewStyle(BrandedTipStyle())
308
-
309
- // Environment-wide (apply to a parent container)
310
262
  NavigationStack {
311
- ContentView()
263
+ PantryListView()
312
264
  }
313
- .tipViewStyle(BrandedTipStyle())
265
+ .tipViewStyle(BrandCardTipStyle())
314
266
  ```
315
267
 
316
- ### Minimal Compact Style
268
+ ### A compact variant
317
269
 
318
- A stripped-down style for tips in tight layouts like toolbars or sidebars.
270
+ For narrow spaces like toolbars and sidebars, a single line on a tinted
271
+ capsule is enough.
319
272
 
320
273
  ```swift
321
- struct CompactTipStyle: TipViewStyle {
274
+ struct SlimTipStyle: TipViewStyle {
322
275
  func makeBody(configuration: Configuration) -> some View {
323
- HStack {
324
- configuration.image?
325
- .foregroundStyle(.tint)
326
-
276
+ Label {
327
277
  configuration.title
328
278
  .font(.caption.bold())
279
+ .lineLimit(1)
280
+ } icon: {
281
+ configuration.image
329
282
  }
330
- .padding(.horizontal)
331
- .padding(.vertical)
332
- .background(.tint.opacity(0.1), in: Capsule())
283
+ .padding(.horizontal, 10)
284
+ .padding(.vertical, 6)
285
+ .background(Color.accentColor.opacity(0.15), in: Capsule())
333
286
  }
334
287
  }
335
288
  ```
336
289
 
337
- ## TipGroup Sequencing
290
+ ## Sequencing with TipGroup (iOS 18)
338
291
 
339
- Use `TipGroup` to present a sequence of onboarding tips. Only the current
340
- tip displays. When the user dismisses or acts on it, the next tip in the
341
- group becomes current.
292
+ A group shows only its current tip. Once that tip is dismissed or acted on,
293
+ the next eligible tip takes its place.
342
294
 
343
295
  ```swift
344
- struct OnboardingTipA: Tip {
345
- var title: Text { Text("Welcome to the App") }
346
- var message: Text? { Text("Let's take a quick tour of the main features.") }
347
- var image: Image? { Image(systemName: "hand.wave") }
348
- }
349
-
350
- struct OnboardingTipB: Tip {
351
- var title: Text { Text("Browse Your Feed") }
352
- var message: Text? { Text("Swipe through curated content tailored for you.") }
353
- var image: Image? { Image(systemName: "rectangle.stack") }
354
- }
355
-
356
- struct OnboardingTipC: Tip {
357
- var title: Text { Text("Customize Your Profile") }
358
- var message: Text? { Text("Tap your avatar to set your name and preferences.") }
359
- var image: Image? { Image(systemName: "person.crop.circle") }
360
- }
361
-
362
- struct HomeView: View {
363
- let tipGroup = TipGroup(.ordered) {
364
- OnboardingTipA()
365
- OnboardingTipB()
366
- OnboardingTipC()
296
+ struct ArticleFeedScreen: View {
297
+ @State private var onboarding = TipGroup(.ordered) {
298
+ FollowTopicTip()
299
+ SaveArticleTip()
300
+ ReaderModeTip()
367
301
  }
368
302
 
369
303
  var body: some View {
370
- VStack {
371
- if let currentTip = tipGroup.currentTip {
372
- TipView(currentTip) { action in
373
- currentTip.invalidate(reason: .actionPerformed)
304
+ ScrollView {
305
+ if let tip = onboarding.currentTip {
306
+ TipView(tip) { _ in
307
+ tip.invalidate(reason: .actionPerformed)
374
308
  }
375
309
  }
376
-
377
- FeedView()
310
+ ArticleFeed()
378
311
  }
379
- .padding()
380
312
  }
381
313
  }
382
314
  ```
383
315
 
384
- ### Tip Group with Popover
316
+ ### Popovers that move between controls
385
317
 
386
- Attach the group's current tip as a popover that moves between controls
387
- as tips advance.
318
+ Give each control a popover for its own tip type by casting
319
+ `currentTip` to that type. As the group advances, the popover jumps to the next control.
388
320
 
389
321
  ```swift
390
- struct ToolbarGroupView: View {
391
- let group = TipGroup(.ordered) {
392
- SearchTip()
393
- FilterTip()
394
- SortTip()
395
- }
396
-
397
- var body: some View {
398
- HStack {
399
- Button("Search", systemImage: "magnifyingglass") { search() }
400
- .popoverTip(group.currentTip as? SearchTip)
401
-
402
- Button("Filter", systemImage: "line.3.horizontal.decrease") { filter() }
403
- .popoverTip(group.currentTip as? FilterTip)
404
-
405
- Button("Sort", systemImage: "arrow.up.arrow.down") { sort() }
406
- .popoverTip(group.currentTip as? SortTip)
407
- }
408
- }
322
+ HStack {
323
+ Button("Follow", systemImage: "plus") { follow() }
324
+ .popoverTip(onboarding.currentTip as? FollowTopicTip)
325
+ Button("Save", systemImage: "bookmark") { save() }
326
+ .popoverTip(onboarding.currentTip as? SaveArticleTip)
409
327
  }
410
328
  ```
411
329
 
412
- ## Testing Strategies
330
+ ## Testing
413
331
 
414
- ### Previewing Tips in SwiftUI Previews
332
+ ### Previews
415
333
 
416
- Configure TipKit in the preview body so tips display in Xcode previews.
417
- Use `showAllTipsForTesting()` to bypass rules.
334
+ Reset the store and force tips on so they render in Xcode previews:
418
335
 
419
336
  ```swift
420
337
  #Preview {
421
- ContentView()
422
- .task {
338
+ PantryListView()
339
+ .task { @MainActor in
423
340
  try? Tips.resetDatastore()
424
341
  Tips.showAllTipsForTesting()
425
342
  }
426
343
  }
427
344
  ```
428
345
 
429
- ### Previewing a Specific Tip
430
-
431
- Show only one tip in a focused preview.
346
+ To look at one tip in isolation, show only that type:
432
347
 
433
348
  ```swift
434
- #Preview("Favorite Tip") {
435
- VStack {
436
- TipView(FavoriteTip())
437
- Spacer()
438
- }
439
- .padding()
440
- .task {
441
- try? Tips.resetDatastore()
442
- Tips.showTipsForTesting([FavoriteTip.self])
443
- }
349
+ #Preview("Filter tip only") {
350
+ PantryListView()
351
+ .task { @MainActor in
352
+ try? Tips.resetDatastore()
353
+ Tips.showTipsForTesting([PantryFilterTip.self])
354
+ }
444
355
  }
445
356
  ```
446
357
 
447
- ### Unit Testing Tip Rules
358
+ ### Unit tests
448
359
 
449
- Verify that parameter and event rules correctly control tip eligibility.
450
- Reset the datastore before each test to ensure a clean state.
360
+ Reset and configure in `setUp`, and reset again in `tearDown` so tests do not
361
+ leak state into each other. Move a tip toward eligibility by setting its
362
+ parameter and donating its event.
451
363
 
452
364
  ```swift
453
365
  import XCTest
454
366
  import TipKit
367
+ @testable import RecipeBox
368
+
369
+ final class BulkExportTipTests: XCTestCase {
370
+ private func clearTipStore() throws {
371
+ try Tips.resetDatastore()
372
+ }
455
373
 
456
- final class TipRuleTests: XCTestCase {
457
374
  override func setUp() async throws {
458
- try? Tips.resetDatastore()
459
- try? Tips.configure()
375
+ try clearTipStore()
376
+ try Tips.configure()
460
377
  }
461
378
 
462
379
  override func tearDown() async throws {
463
- try? Tips.resetDatastore()
380
+ try clearTipStore()
464
381
  }
465
382
 
466
- func testAdvancedSearchTipRequiresLogin() async {
467
- let tip = AdvancedSearchTip()
468
-
469
- // Tip should not be eligible before login
470
- AdvancedSearchTip.isLoggedIn = false
471
- // Verify tip status
472
-
473
- // Tip should become eligible after login + enough events
474
- AdvancedSearchTip.isLoggedIn = true
475
- for _ in 0..<3 {
476
- AdvancedSearchTip.searchPerformed.donate()
383
+ func testSignedInUserAfterThreeExports_BecomesEligible() async {
384
+ BulkExportTip.isSignedIn = true
385
+ for _ in 1...3 {
386
+ await BulkExportTip.recipeExported.donate()
477
387
  }
478
- // Verify tip status
388
+ let tip = BulkExportTip()
389
+ // Assert on the tip's status here once eligibility is observable.
390
+ _ = tip
479
391
  }
480
392
 
481
- func testTipInvalidation() async {
482
- let tip = FavoriteTip()
393
+ func testInvalidatedTip_IsNoLongerEligible() async {
394
+ let tip = BulkExportTip()
483
395
  tip.invalidate(reason: .actionPerformed)
484
- // Tip should no longer be eligible after invalidation
396
+ // Assert the tip reports an invalidated status here.
485
397
  }
486
398
  }
487
399
  ```
488
400
 
489
- ### UI Testing with Forced Tips
401
+ The status assertions are left as placeholders; how you read a tip's status
402
+ depends on the TipKit API available to the test target.
490
403
 
491
- Pass launch arguments to control tip visibility in UI tests. This ensures
492
- tests that verify tip UI always see the tip, regardless of rules.
404
+ ### UI tests
405
+
406
+ Force tips on from the test by adding a launch argument before `launch()`.
407
+ `XCUIApplication` is main-actor isolated, so mark the test `@MainActor`:
493
408
 
494
409
  ```swift
495
- // In UI test setUp
496
- let app = XCUIApplication()
497
- app.launchArguments.append("--show-all-tips")
498
- app.launch()
410
+ @MainActor
411
+ func testFilterTipAppears() {
412
+ let app = XCUIApplication()
413
+ app.launchArguments += ["--show-all-tips"]
414
+ app.launch()
415
+ }
499
416
  ```
500
417
 
418
+ Tests that have nothing to do with tips pass `--hide-all-tips` instead so no
419
+ tip covers the controls under test. The app reads both flags in `init`,
420
+ before configuring:
421
+
501
422
  ```swift
502
- // In App.init
503
- init() {
504
- if ProcessInfo.processInfo.arguments.contains("--show-all-tips") {
423
+ enum TipTestFlag: String {
424
+ case showAll = "--show-all-tips"
425
+ case hideAll = "--hide-all-tips"
426
+ }
427
+
428
+ func applyTipTestFlags() {
429
+ let arguments = ProcessInfo.processInfo.arguments
430
+ if arguments.contains(TipTestFlag.showAll.rawValue) {
505
431
  Tips.showAllTipsForTesting()
506
- }
507
- if ProcessInfo.processInfo.arguments.contains("--hide-all-tips") {
432
+ } else if arguments.contains(TipTestFlag.hideAll.rawValue) {
508
433
  Tips.hideAllTipsForTesting()
509
434
  }
510
- try? Tips.configure()
511
435
  }
512
- ```
513
-
514
- ### UI Testing Without Tips
515
-
516
- Suppress all tips in UI tests that are not about tip behavior, so tips
517
- do not interfere with other test flows.
518
436
 
519
- ```swift
520
- // In UI test setUp for non-tip tests
521
- let app = XCUIApplication()
522
- app.launchArguments.append("--hide-all-tips")
523
- app.launch()
437
+ init() {
438
+ #if DEBUG
439
+ applyTipTestFlags()
440
+ #endif
441
+ try? Tips.configure()
442
+ }
524
443
  ```
525
444
 
526
- ## Tip with Action Buttons
445
+ ## Actions that open a feature
527
446
 
528
- Add action buttons that deep-link to a feature. Invalidate the tip when
529
- the user taps the primary action.
447
+ A primary action can take the person straight into the feature: it sets the
448
+ presentation state and invalidates the tip as performed. A "Not Now" style
449
+ secondary action closes the tip with `.tipClosed`.
530
450
 
531
451
  ```swift
532
- struct NewEditorTip: Tip {
533
- var title: Text {
534
- Text("Try the New Editor")
535
- }
536
-
537
- var message: Text? {
538
- Text("A faster, more powerful editing experience awaits.")
539
- }
540
-
541
- var image: Image? {
542
- Image(systemName: "pencil.and.outline")
543
- }
452
+ struct MealPlanTip: Tip {
453
+ var title: Text { Text("Plan your week") }
544
454
 
545
455
  var actions: [Action] {
546
- Action(id: "open-editor", title: "Open Editor")
547
- Action(id: "later", title: "Maybe Later")
456
+ Action(id: "start-plan", title: "Start a Plan")
457
+ Action(id: "not-now", title: "Not Now")
548
458
  }
549
459
  }
550
460
 
551
- struct HomeView: View {
552
- let editorTip = NewEditorTip()
553
- @State private var showEditor = false
461
+ struct PlannerEntryView: View {
462
+ let planTip = MealPlanTip()
463
+ @State private var isPlannerShown = false
554
464
 
555
465
  var body: some View {
556
- VStack {
557
- TipView(editorTip) { action in
558
- switch action.id {
559
- case "open-editor":
560
- showEditor = true
561
- editorTip.invalidate(reason: .actionPerformed)
562
- case "later":
563
- editorTip.invalidate(reason: .tipClosed)
564
- default:
565
- break
566
- }
466
+ TipView(planTip) { action in
467
+ if action.id == "start-plan" {
468
+ isPlannerShown = true
469
+ planTip.invalidate(reason: .actionPerformed)
470
+ } else {
471
+ planTip.invalidate(reason: .tipClosed)
567
472
  }
568
-
569
- MainContentView()
570
473
  }
571
- .sheet(isPresented: $showEditor) {
572
- EditorView()
474
+ .sheet(isPresented: $isPlannerShown) {
475
+ MealPlannerView()
573
476
  }
574
477
  }
575
478
  }
576
479
  ```
577
480
 
578
- ## Integration with Onboarding Flow
481
+ ## Working alongside an onboarding flow
579
482
 
580
- Coordinate TipKit with a first-run onboarding flow. Invalidate welcome
581
- tips after the user completes onboarding so they do not see redundant
582
- information.
483
+ Give both tips a `didFinishOnboarding` parameter (the has-completed-onboarding
484
+ flag). A welcome tip uses
485
+ `== false`, so it only appears for people who skipped onboarding. A discovery
486
+ tip uses `== true`, so it only appears once onboarding is done.
583
487
 
584
488
  ```swift
585
489
  struct WelcomeTip: Tip {
586
- @Parameter
587
- static var hasCompletedOnboarding: Bool = false
490
+ var title: Text { Text("Your recipes, all in one place") }
588
491
 
589
- var title: Text { Text("Welcome to MyApp") }
590
- var message: Text? { Text("Swipe through to learn the basics.") }
492
+ @Parameter
493
+ static var didFinishOnboarding: Bool = false
591
494
 
592
495
  var rules: [Rule] {
593
- // Only show if onboarding was NOT completed (user skipped it)
594
- #Rule(Self.$hasCompletedOnboarding) { $0 == false }
496
+ #Rule(Self.$didFinishOnboarding) { $0 == false }
595
497
  }
596
498
  }
597
499
 
598
- struct FeatureDiscoveryTip: Tip {
599
- @Parameter
600
- static var hasCompletedOnboarding: Bool = false
500
+ struct CollectionsTip: Tip {
501
+ var title: Text { Text("Group recipes into collections") }
601
502
 
602
- var title: Text { Text("Discover Collections") }
603
- var message: Text? { Text("Organize your items into collections for easy access.") }
503
+ @Parameter
504
+ static var didFinishOnboarding: Bool = false
604
505
 
605
506
  var rules: [Rule] {
606
- // Only show after onboarding completes
607
- #Rule(Self.$hasCompletedOnboarding) { $0 == true }
507
+ #Rule(Self.$didFinishOnboarding) { $0 == true }
608
508
  }
609
509
  }
510
+ ```
610
511
 
611
- struct OnboardingView: View {
612
- @Binding var isPresented: Bool
613
-
614
- var body: some View {
615
- VStack {
616
- // Onboarding pages...
617
-
618
- Button("Get Started") {
619
- completeOnboarding()
620
- }
621
- }
622
- }
623
-
624
- func completeOnboarding() {
625
- // Invalidate welcome tips since onboarding covered the basics
626
- WelcomeTip.hasCompletedOnboarding = true
627
- FeatureDiscoveryTip.hasCompletedOnboarding = true
512
+ When onboarding finishes, flip both parameters, invalidate the welcome tip
513
+ explicitly, and close the sheet. The host view below does this in the
514
+ onboarding completion closure.
628
515
 
629
- // Explicitly invalidate any welcome-specific tips
630
- let welcomeTip = WelcomeTip()
631
- welcomeTip.invalidate(reason: .actionPerformed)
632
-
633
- isPresented = false
634
- }
635
- }
636
-
637
- struct ContentView: View {
638
- @AppStorage("hasCompletedOnboarding") private var hasCompletedOnboarding = false
639
- @State private var showOnboarding = false
516
+ The host view remembers completion in `@AppStorage`, presents onboarding in a
517
+ sheet on first appearance, and shows both tips:
640
518
 
519
+ ```swift
520
+ struct RecipeBoxHomeView: View {
521
+ @AppStorage("onboardingDone") private var onboardingDone = false
522
+ @State private var isOnboardingShown = false
641
523
  let welcomeTip = WelcomeTip()
642
- let discoveryTip = FeatureDiscoveryTip()
524
+ let collectionsTip = CollectionsTip()
643
525
 
644
526
  var body: some View {
645
- NavigationStack {
646
- VStack {
647
- TipView(welcomeTip)
648
-
649
- CollectionGrid()
650
- .popoverTip(discoveryTip)
651
- }
652
- }
653
- .sheet(isPresented: $showOnboarding) {
654
- OnboardingView(isPresented: $showOnboarding)
527
+ VStack {
528
+ TipView(welcomeTip)
529
+ RecipeGrid()
530
+ .popoverTip(collectionsTip)
655
531
  }
656
532
  .onAppear {
657
- if !hasCompletedOnboarding {
658
- showOnboarding = true
533
+ if onboardingDone == false {
534
+ isOnboardingShown = true
535
+ }
536
+ }
537
+ .sheet(isPresented: $isOnboardingShown) {
538
+ OnboardingView {
539
+ onboardingDone = true
540
+ WelcomeTip.didFinishOnboarding = true
541
+ CollectionsTip.didFinishOnboarding = true
542
+ welcomeTip.invalidate(reason: .actionPerformed)
543
+ isOnboardingShown = false
659
544
  }
660
545
  }
661
546
  }
662
547
  }
663
548
  ```
664
549
 
665
- ## Full App Integration Example
550
+ ## A full app
666
551
 
667
- A complete example showing TipKit configuration, multiple tips with rules,
668
- a tip group, event donations, and proper invalidation.
552
+ Three tips: one with no rules, one that waits for three "item added"
553
+ donations, and one that waits until a collection exists.
669
554
 
670
555
  ```swift
671
556
  import SwiftUI
672
557
  import TipKit
673
558
 
674
- // MARK: - Tips
675
-
676
- struct SearchTip: Tip {
677
- var title: Text { Text("Search Your Library") }
678
- var message: Text? { Text("Tap to find any item by name, tag, or date.") }
679
- var image: Image? { Image(systemName: "magnifyingglass") }
559
+ struct ScanBarcodeTip: Tip {
560
+ var title: Text { Text("Scan to add") }
561
+ var image: Image? { Image(systemName: "barcode.viewfinder") }
680
562
  }
681
563
 
682
- struct CollectionTip: Tip {
683
- static let itemAddedEvent = Tips.Event(id: "itemAdded")
564
+ struct BatchEditTip: Tip {
565
+ var title: Text { Text("Edit several items at once") }
684
566
 
685
- var title: Text { Text("Create a Collection") }
686
- var message: Text? { Text("Group related items together for quick access.") }
687
- var image: Image? { Image(systemName: "folder.badge.plus") }
567
+ static let itemAdded = Tips.Event(id: "itemAdded")
688
568
 
689
569
  var rules: [Rule] {
690
- #Rule(Self.itemAddedEvent) { $0.donations.count >= 3 }
570
+ #Rule(Self.itemAdded) { $0.donations.count >= 3 }
691
571
  }
692
572
  }
693
573
 
694
- struct ShareTip: Tip {
695
- @Parameter
696
- static var hasCreatedCollection: Bool = false
574
+ struct ShareCollectionTip: Tip {
575
+ var title: Text { Text("Send a collection to a friend") }
697
576
 
698
- var title: Text { Text("Share Your Collection") }
699
- var message: Text? { Text("Invite others to view or collaborate on your collection.") }
700
- var image: Image? { Image(systemName: "square.and.arrow.up") }
577
+ @Parameter
578
+ static var hasCollection: Bool = false
701
579
 
702
580
  var rules: [Rule] {
703
- #Rule(Self.$hasCreatedCollection) { $0 == true }
581
+ #Rule(Self.$hasCollection) { $0 == true }
704
582
  }
705
583
  }
706
584
 
707
- // MARK: - App
708
-
709
585
  @main
710
- struct LibraryApp: App {
586
+ struct PantryApp: App {
587
+ var body: some Scene {
588
+ WindowGroup {
589
+ NavigationStack {
590
+ PantryHomeView()
591
+ }
592
+ }
593
+ }
594
+
711
595
  init() {
712
596
  #if DEBUG
713
- if ProcessInfo.processInfo.arguments.contains("--show-all-tips") {
714
- Tips.showAllTipsForTesting()
715
- }
716
- if ProcessInfo.processInfo.arguments.contains("--hide-all-tips") {
717
- Tips.hideAllTipsForTesting()
718
- }
597
+ applyTipTestFlags()
719
598
  #endif
720
-
721
599
  try? Tips.configure([
722
- .displayFrequency(.daily),
723
- .datastoreLocation(.applicationDefault)
600
+ .datastoreLocation(.applicationDefault),
601
+ .displayFrequency(.daily)
724
602
  ])
725
603
  }
726
-
727
- var body: some Scene {
728
- WindowGroup { LibraryView() }
729
- }
730
604
  }
731
605
 
732
- // MARK: - Main View
733
-
734
- struct LibraryView: View {
735
- let searchTip = SearchTip()
736
- let collectionTip = CollectionTip()
737
- let shareTip = ShareTip()
738
-
739
- @State private var items: [LibraryItem] = []
606
+ struct PantryHomeView: View {
607
+ let scanTip = ScanBarcodeTip()
608
+ let batchTip = BatchEditTip()
609
+ let shareTip = ShareCollectionTip()
740
610
 
741
611
  var body: some View {
742
- NavigationStack {
743
- List {
744
- TipView(collectionTip)
745
-
746
- ForEach(items) { item in
747
- Text(item.name)
612
+ List {
613
+ TipView(scanTip)
614
+ PantryItemsSection()
615
+ }
616
+ .toolbar {
617
+ ToolbarItem(placement: .primaryAction) {
618
+ Button("Edit", systemImage: "checklist") {
619
+ batchTip.invalidate(reason: .actionPerformed)
620
+ beginBatchEdit()
748
621
  }
622
+ .popoverTip(batchTip)
749
623
  }
750
- .navigationTitle("Library")
751
- .toolbar {
752
- ToolbarItem(placement: .primaryAction) {
753
- Button("Search", systemImage: "magnifyingglass") {
754
- showSearch()
755
- searchTip.invalidate(reason: .actionPerformed)
756
- }
757
- .popoverTip(searchTip)
624
+ ToolbarItem(placement: .secondaryAction) {
625
+ Button("Send", systemImage: "person.2") {
626
+ shareTip.invalidate(reason: .actionPerformed)
627
+ shareCollection()
758
628
  }
759
-
760
- ToolbarItem(placement: .secondaryAction) {
761
- Button("Share", systemImage: "square.and.arrow.up") {
762
- shareCollection()
763
- shareTip.invalidate(reason: .actionPerformed)
764
- }
765
- .popoverTip(shareTip)
766
- }
767
-
768
- ToolbarItem(placement: .secondaryAction) {
769
- Button("Add Item", systemImage: "plus") {
770
- addItem()
771
- CollectionTip.itemAddedEvent.donate()
772
- }
629
+ .popoverTip(shareTip)
630
+ }
631
+ ToolbarItem(placement: .bottomBar) {
632
+ Button("Add Item") {
633
+ addItem()
634
+ Task { await BatchEditTip.itemAdded.donate() }
773
635
  }
774
636
  }
775
637
  }
776
638
  }
777
-
778
- func addItem() { /* ... */ }
779
- func showSearch() { /* ... */ }
780
- func shareCollection() { /* ... */ }
781
639
  }
782
640
  ```
641
+
642
+ The "Add Item" button carries no tip of its own; it only donates the event
643
+ that eventually makes `BatchEditTip` eligible. `applyTipTestFlags()` is the
644
+ DEBUG helper from the UI tests section.