@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,276 +1,281 @@
1
1
  ---
2
2
  name: push-notifications
3
- description: "Implement, review, or debug push notifications in iOS/macOS apps - local notifications, remote (APNs) notifications, rich notifications, notification actions, silent pushes, and notification service/content extensions. Use when working with UNUserNotificationCenter, registering for remote notifications, handling notification payloads, setting up notification categories and actions, creating rich notification content, or debugging notification delivery. Also use when working with alerts, badges, sounds, background pushes, or user notification permissions in Swift apps."
3
+ description: "UserNotifications and APNs on iOS and macOS, local and remote: permission (standard, provisional, critical), device-token registration, triggers, payloads (alert, background, mutable, localized), foreground presentation, tap handling and deep links, categories and actions, grouping, service and content extensions, media attachments, communication notifications, delivery debugging. Use when building, reviewing or debugging notifications, badges, sounds, silent pushes or notification permission. Not for Live Activity, VoIP or App Clip pushes, or post-push background work."
4
4
  metadata:
5
- source: "dpearson2699/swift-ios-skills (PolyForm Perimeter 1.0.0)"
5
+ source: multi-agent-pipeline
6
6
  ---
7
7
 
8
8
  # Push Notifications
9
9
 
10
- Implement, review, and debug local and remote notifications on iOS/macOS using `UserNotifications` and APNs. Covers permission flow, token registration, payload structure, foreground handling, notification actions, grouping, and rich notifications. Targets iOS 26+ with Swift 6.3, backward-compatible to iOS 16 unless noted.
11
-
12
- Keep adjacent domains separate: Live Activity `content-state` payloads belong in `activitykit`; PushKit/VoIP call pushes belong in `callkit`; App Clip ephemeral notification setup belongs in `app-clips`; long-running or scheduled background work after a silent push belongs in `background-processing`.
13
-
14
- ## Contents
15
-
16
- - [Correction Reviews](#correction-reviews)
17
- - [Permission Flow](#permission-flow)
18
- - [APNs Registration](#apns-registration)
19
- - [Local Notifications](#local-notifications)
20
- - [Remote Notification Payload](#remote-notification-payload)
21
- - [Notification Handling](#notification-handling)
22
- - [Notification Actions and Categories](#notification-actions-and-categories)
23
- - [Notification Grouping](#notification-grouping)
24
- - [Common Mistakes](#common-mistakes)
25
- - [Review Checklist](#review-checklist)
26
- - [References](#references)
27
-
28
- ## Correction Reviews
29
-
30
- When reviewing flawed notification proposals, explicitly name the violated contract. APNs token reviews must say token registration is independent from alert authorization, upload on every `didRegister` callback, avoid local-cache-as-truth logic, never assume token length, and treat Simulator registration failure as expected while noting `.apns` files or `simctl push` can simulate delivery. Background-push reviews must say `content-available` only, `apns-push-type: background`, `apns-priority: 5`, Remote notifications background mode, low priority, throttled, not guaranteed, not every few minutes, and bounded `didReceiveRemoteNotification` returning the correct `UIBackgroundFetchResult`. Rich-notification reviews must say service extensions require `mutable-content: 1` plus an alert payload, silent pushes do not trigger them, attachments are supported on-disk files that the system validates and stores, secrets use Keychain Sharing while App Groups are for shared files/UserDefaults, communication notifications require capability + `NSUserActivityTypes` + `INInteraction` donation + `content.updating(from:)`, and every service-extension path including attachment/download failures and `serviceExtensionTimeWillExpire()` must call the content handler exactly once with original, best-attempt, or updated content.
31
-
32
- ## Permission Flow
33
-
34
- Request notification authorization before scheduling or displaying user-visible alerts, sounds, or badges. The system prompt appears only once; subsequent calls return the stored decision. APNs token registration is separate: call `registerForRemoteNotifications()` when the app needs a device token, even if the user hasn't granted alert authorization.
10
+ Notifications on Apple platforms come from two places: the device itself
11
+ (local notifications scheduled through `UserNotifications`) and a server that
12
+ talks to the Apple Push Notification service (APNs). Both end up in the same
13
+ `UNUserNotificationCenter`, so permission, presentation, actions and grouping
14
+ work the same way for each.
15
+
16
+ Baseline: iOS 26 and Swift 6.3. Everything here also works back to iOS 16
17
+ unless a section names a newer release.
18
+
19
+ Hand these to their own skills:
20
+
21
+ | Topic | Skill |
22
+ |-------|-------|
23
+ | Live Activity `content-state` pushes | `live-activities` |
24
+ | PushKit and VoIP call pushes | `callkit-voip` |
25
+ | Ephemeral App Clip notifications | `app-clips` |
26
+ | Scheduled or long background work triggered by a push | `background-processing` |
27
+
28
+ Longer material:
29
+ the [app-side patterns](references/notification-patterns.md) (launch wiring,
30
+ the delegate, routing taps to screens, silent-push handling, a scheduler, the
31
+ token lifecycle, testing, badges) and the
32
+ [extensions guide](references/rich-notifications.md) (service and content
33
+ extensions, attachments, communication notifications).
34
+
35
+ ## Reviewing Someone Else's Design
36
+
37
+ When a proposal is wrong, say which contract it breaks, then give the fix.
38
+ "Move this call" is not enough; "registration does not depend on alert
39
+ permission, so this gate blocks silent pushes" is. The contracts that come up
40
+ most often:
41
+
42
+ **Device token**
43
+ - Getting an APNs token and getting alert permission are two separate things.
44
+ - Send the token to the server from every `didRegister` callback.
45
+ - A token saved on the device is a cache, never the truth.
46
+ - Token length is not fixed; never hard-code or validate a size.
47
+ - On Simulator, registration fails by design. Pushes can still be simulated
48
+ with a `.apns` file or `xcrun simctl push`.
49
+
50
+ **Background (silent) push**
51
+ - Payload carries `content-available: 1` and nothing that alerts: no alert, sound or badge.
52
+ - Send it at priority 5 (`apns-priority: 5`) with the header `apns-push-type: background`.
53
+ - Turn on Remote notifications under the app's Background Modes capability.
54
+ - APNs treats it as low priority and throttles it; it may never arrive. It is
55
+ not a scheduler, so a cadence of one every few minutes will not happen.
56
+ - `didReceiveRemoteNotification` does short, bounded work and returns the
57
+ `UIBackgroundFetchResult` that matches what actually happened.
58
+
59
+ **Rich notifications**
60
+ - A service extension runs only for an alerting push with `mutable-content: 1`.
61
+ A silent push never reaches it.
62
+ - Attachments must be supported files already on disk; the system checks them
63
+ and moves them into its own store.
64
+ - Secrets such as decryption keys live in a shared Keychain access group. App
65
+ Groups are for shared files and `UserDefaults` only.
66
+ - Communication notifications need four things: the capability, an
67
+ `NSUserActivityTypes` entry, a donated `INInteraction`, and a call to
68
+ `content.updating(from:)`.
69
+ - The content handler is called exactly once on every path: success, failed
70
+ download, failed decryption, and `serviceExtensionTimeWillExpire()`. It
71
+ receives the updated content, the best attempt so far, or the original.
72
+
73
+ ## Permission
74
+
75
+ Ask before scheduling anything the user will see or hear, or before setting a
76
+ badge. iOS shows the system prompt a single time; after that the call returns
77
+ whatever the user chose. Registering with APNs is a different path and may
78
+ happen even if the user said no to alerts.
35
79
 
36
80
  ```swift
37
- import UserNotifications
38
-
39
81
  @MainActor
40
- func requestNotificationPermission() async -> Bool {
41
- let center = UNUserNotificationCenter.current()
82
+ func askForAlertPermission() async -> Bool {
42
83
  do {
43
- let granted = try await center.requestAuthorization(
44
- options: [.alert, .sound, .badge]
45
- )
46
- return granted
84
+ return try await UNUserNotificationCenter.current()
85
+ .requestAuthorization(options: [.alert, .sound, .badge])
47
86
  } catch {
48
- print("Authorization request failed: \(error)")
49
87
  return false
50
88
  }
51
89
  }
52
90
  ```
53
91
 
54
- ### Checking Current Status
92
+ ### Reading the current setting
55
93
 
56
- Always check status before assuming permissions. The user can change settings at any time.
94
+ The user can change the setting in Settings at any moment, so read it instead
95
+ of remembering an old answer.
57
96
 
58
97
  ```swift
59
- @MainActor
60
- func checkNotificationStatus() async -> UNAuthorizationStatus {
61
- let settings = await UNUserNotificationCenter.current().notificationSettings()
62
- return settings.authorizationStatus
63
- // .notDetermined, .denied, .authorized, .provisional, .ephemeral
98
+ let center = UNUserNotificationCenter.current()
99
+ let current = await center.notificationSettings()
100
+ switch current.authorizationStatus {
101
+ case .notDetermined: break // never asked
102
+ case .denied: break // user said no
103
+ case .authorized: break
104
+ case .provisional: break // quiet delivery, no prompt shown
105
+ case .ephemeral: break // App Clip
106
+ @unknown default: break
64
107
  }
65
108
  ```
66
109
 
67
- ### Provisional Notifications
68
-
69
- Provisional notifications deliver quietly to the notification center without interrupting the user. The user can then choose to keep or turn them off. Use for onboarding flows where you want to demonstrate value before asking for full permission.
110
+ ### Provisional authorization
70
111
 
71
- ```swift
72
- // Delivers silently -- no permission prompt shown to the user
73
- try await center.requestAuthorization(options: [.alert, .sound, .badge, .provisional])
74
- ```
112
+ Adding `.provisional` to the options skips the prompt. Notifications go
113
+ straight to Notification Center without sound or banner, and the user decides
114
+ later whether to keep them. It suits onboarding: show value first, ask for full
115
+ permission afterwards.
75
116
 
76
- ### Critical Alerts
117
+ ### Critical alerts
77
118
 
78
- Critical alerts bypass Do Not Disturb and the mute switch. Requires a special entitlement from Apple (request via developer portal). Use only for health, safety, or security scenarios.
79
-
80
- ```swift
81
- // Requires com.apple.developer.usernotifications.critical-alerts entitlement
82
- try await center.requestAuthorization(
83
- options: [.alert, .sound, .badge, .criticalAlert]
84
- )
85
- ```
119
+ `.criticalAlert` plays through Do Not Disturb and the ring/silent switch. It
120
+ needs the `com.apple.developer.usernotifications.critical-alerts` entitlement,
121
+ which Apple grants on request through the developer portal. Reserve it for
122
+ health, safety and security situations.
86
123
 
87
- ### Handling Denied Permissions
124
+ ### When the user said no
88
125
 
89
- When the user has denied notifications, guide them to Settings with `UIApplication.openSettingsURLString`. Do not repeatedly prompt or nag.
126
+ Do not ask again and do not nag. Offer a button that opens the app's page in
127
+ Settings through `UIApplication.openSettingsURLString`.
90
128
 
91
- ## APNs Registration
129
+ ## Registering with APNs
92
130
 
93
- Use `UIApplicationDelegateAdaptor` to receive the device token in a SwiftUI app. The AppDelegate callbacks are the only way to receive APNs tokens.
131
+ A SwiftUI app still needs an app delegate for this: the device token is only
132
+ delivered to `UIApplicationDelegate` callbacks. Attach one with
133
+ `@UIApplicationDelegateAdaptor`.
94
134
 
95
135
  ```swift
96
- @main
97
- struct MyApp: App {
98
- @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
99
-
100
- var body: some Scene {
101
- WindowGroup {
102
- ContentView()
103
- }
104
- }
105
- }
106
-
107
- class AppDelegate: NSObject, UIApplicationDelegate {
136
+ final class AppDelegate: NSObject, UIApplicationDelegate {
108
137
  func application(
109
138
  _ application: UIApplication,
110
- didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
139
+ didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]? = nil
111
140
  ) -> Bool {
112
- UNUserNotificationCenter.current().delegate = NotificationDelegate.shared
141
+ UNUserNotificationCenter.current().delegate = InboxNotificationDelegate.shared
113
142
  return true
114
143
  }
115
144
 
116
- func application(
117
- _ application: UIApplication,
118
- didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
119
- ) {
120
- let token = deviceToken.map { String(format: "%02x", $0) }.joined()
121
- print("APNs token: \(token)")
122
- // Send token to your server
123
- Task { await TokenService.shared.upload(token: token) }
145
+ func application(_ application: UIApplication,
146
+ didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
147
+ let hex = deviceToken.reduce(into: "") { $0 += String(format: "%02x", $1) }
148
+ Task { await DeviceTokenUploader.shared.send(hex) }
124
149
  }
125
150
 
126
- func application(
127
- _ application: UIApplication,
128
- didFailToRegisterForRemoteNotificationsWithError error: Error
129
- ) {
130
- print("APNs registration failed: \(error.localizedDescription)")
131
- // Simulator can simulate pushes, but it does not register with APNs.
151
+ func application(_ application: UIApplication,
152
+ didFailToRegisterForRemoteNotificationsWithError error: Error) {
153
+ // Expected on Simulator: it can show simulated pushes but never gets an APNs token.
132
154
  }
133
155
  }
134
156
  ```
135
157
 
136
- ### Registration Order
158
+ ### Order of calls
137
159
 
138
- Configure delegates and categories at launch. Then request user-notification authorization in context for visible notifications, and register with APNs whenever the app needs a device token. Do not gate APNs registration on `.authorized`; without alert authorization, remote notifications are delivered silently.
160
+ 1. At launch: set the delegate and register categories.
161
+ 2. At a moment that makes sense to the user: ask for alert permission.
162
+ 3. Whenever a token is needed: call `registerForRemoteNotifications()`.
163
+
164
+ Do not wait for `.authorized` before step 3. An app without alert permission
165
+ still receives remote notifications, just silently, and the server still needs
166
+ the token.
139
167
 
140
168
  ```swift
141
169
  @MainActor
142
- func configureNotifications() async {
170
+ func prepareNotifications() async {
143
171
  let center = UNUserNotificationCenter.current()
144
- let settings = await center.notificationSettings()
145
-
146
- if settings.authorizationStatus == .notDetermined {
147
- _ = await requestNotificationPermission()
172
+ if await center.notificationSettings().authorizationStatus == .notDetermined {
173
+ _ = try? await center.requestAuthorization(options: [.alert, .badge, .sound])
148
174
  }
149
-
150
- // Needed for APNs token delivery and silent remote notifications.
151
- UIApplication.shared.registerForRemoteNotifications()
175
+ UIApplication.shared.registerForRemoteNotifications() // token + silent pushes
152
176
  }
153
177
  ```
154
178
 
155
- ### Token Handling
179
+ ### Token rules
156
180
 
157
- Device tokens change. Re-send the token to your server every time `didRegisterForRemoteNotificationsWithDeviceToken` fires, not just the first time. Do not persist tokens locally as a source of truth or assume a fixed token length.
181
+ The token can change (restore, reinstall, new device, OS update). Upload it
182
+ from every `didRegisterForRemoteNotificationsWithDeviceToken` call, not only the
183
+ first one. Do not keep a local copy as the source of truth, and do not assume a
184
+ length.
158
185
 
159
186
  ## Local Notifications
160
187
 
161
- Schedule notifications directly from the device without a server. Useful for reminders, timers, and location-based alerts.
162
-
163
- ### Creating Content
188
+ Local notifications need no server: the device schedules and shows them.
189
+ Reminders, timers and arriving at a place are the usual cases.
164
190
 
165
191
  ```swift
166
192
  let content = UNMutableNotificationContent()
167
- content.title = "Workout Reminder"
168
- content.subtitle = "Time to move"
169
- content.body = "You have a scheduled workout in 15 minutes."
193
+ content.title = "Plants"
194
+ content.subtitle = "Balcony"
195
+ content.body = "The basil has not been watered for three days."
170
196
  content.sound = .default
171
197
  content.badge = NSNumber(value: 1)
172
- content.userInfo = ["workoutId": "abc123"]
173
- content.threadIdentifier = "workouts" // groups in notification center
198
+ content.userInfo = ["plantID": "basil-01"]
199
+ content.threadIdentifier = "garden" // groups these together in Notification Center
174
200
  ```
175
201
 
176
- ### Trigger Types
202
+ ### Triggers
177
203
 
178
204
  ```swift
179
- // Fire after a time interval (minimum 60 seconds for repeating)
180
- let timeTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 300, repeats: false)
181
-
182
- // Fire at a specific date/time
183
- var dateComponents = DateComponents()
184
- dateComponents.hour = 8
185
- dateComponents.minute = 30
186
- let calendarTrigger = UNCalendarNotificationTrigger(
187
- dateMatching: dateComponents, repeats: true // daily at 8:30 AM
188
- )
189
-
190
- // Fire when entering a geographic region
191
- let region = CLCircularRegion(
192
- center: CLLocationCoordinate2D(latitude: 37.33, longitude: -122.01),
193
- radius: 100,
205
+ // Once, 15 minutes from now. A repeating interval must be at least 60 seconds.
206
+ let soon = UNTimeIntervalNotificationTrigger(timeInterval: 900, repeats: false)
207
+
208
+ // Every day at 07:30.
209
+ var morning = DateComponents()
210
+ morning.hour = 7
211
+ morning.minute = 30
212
+ let daily = UNCalendarNotificationTrigger(dateMatching: morning, repeats: true)
213
+
214
+ // On arriving at a place. Needs at least When In Use location permission.
215
+ let gym = CLCircularRegion(
216
+ center: CLLocationCoordinate2D(latitude: 52.37, longitude: 4.89),
217
+ radius: 150,
194
218
  identifier: "gym"
195
219
  )
196
- region.notifyOnEntry = true
197
- region.notifyOnExit = false
198
- let locationTrigger = UNLocationNotificationTrigger(region: region, repeats: false)
199
- // Requires "When In Use" location permission at minimum
220
+ gym.notifyOnEntry = true
221
+ gym.notifyOnExit = false
222
+ let arrival = UNLocationNotificationTrigger(region: gym, repeats: false)
200
223
  ```
201
224
 
202
- ### Scheduling and Managing
225
+ ### Scheduling and cleanup
203
226
 
204
227
  ```swift
205
- let request = UNNotificationRequest(
206
- identifier: "workout-reminder-abc123",
207
- content: content,
208
- trigger: timeTrigger
209
- )
210
-
211
228
  let center = UNUserNotificationCenter.current()
229
+ let request = UNNotificationRequest(identifier: "water-basil", content: content, trigger: daily)
212
230
  try await center.add(request)
213
231
 
214
- // Remove specific pending notifications
215
- center.removePendingNotificationRequests(withIdentifiers: ["workout-reminder-abc123"])
232
+ let waiting = await center.pendingNotificationRequests()
216
233
 
217
- // Remove all pending
234
+ center.removePendingNotificationRequests(withIdentifiers: ["water-basil"])
218
235
  center.removeAllPendingNotificationRequests()
219
-
220
- // Remove delivered notifications from notification center
221
- center.removeDeliveredNotifications(withIdentifiers: ["workout-reminder-abc123"])
236
+ center.removeDeliveredNotifications(withIdentifiers: ["water-basil"])
222
237
  center.removeAllDeliveredNotifications()
223
-
224
- // List all pending requests
225
- let pending = await center.pendingNotificationRequests()
226
238
  ```
227
239
 
228
- ## Remote Notification Payload
240
+ ## Remote Payloads
229
241
 
230
- ### Standard APNs Payload
242
+ ### Alert push
231
243
 
232
244
  ```json
233
245
  {
234
- "aps": {
235
- "alert": {
236
- "title": "New Message",
237
- "subtitle": "From Alice",
238
- "body": "Hey, are you free for lunch?"
239
- },
240
- "badge": 3,
241
- "sound": "default",
242
- "thread-id": "chat-alice",
243
- "category": "MESSAGE_CATEGORY"
244
- },
245
- "messageId": "msg-789",
246
- "senderId": "user-alice"
246
+ "aps": {
247
+ "alert": { "title": "Order shipped", "subtitle": "#4821", "body": "Arrives Thursday." },
248
+ "badge": 2,
249
+ "sound": "default",
250
+ "thread-id": "orders",
251
+ "category": "ORDER_UPDATE"
252
+ },
253
+ "orderID": "4821"
247
254
  }
248
255
  ```
249
256
 
250
- ### Silent / Background Push
257
+ App-specific keys go beside `aps`, never inside it.
251
258
 
252
- Set `content-available: 1` with no alert, sound, or badge. Requires "Background Modes > Remote notifications" plus APNs headers `apns-push-type: background` and `apns-priority: 5`. The system treats these as low priority, throttled, and not guaranteed; do not send them every few minutes or rely on them for immediate freshness. In `didReceiveRemoteNotification`, do bounded work and return a `UIBackgroundFetchResult` promptly, within the background execution window.
259
+ ### Background push
253
260
 
254
261
  ```json
255
- {
256
- "aps": {
257
- "content-available": 1
258
- },
259
- "updateType": "new-data"
260
- }
262
+ { "aps": { "content-available": 1 }, "syncScope": "inbox" }
261
263
  ```
262
264
 
263
- Handle in AppDelegate:
265
+ - No `alert`, `sound` or `badge`.
266
+ - Background Modes > Remote notifications must be on.
267
+ - Header `apns-push-type: background`, priority `apns-priority: 5`.
268
+ - Expect throttling and occasional drops. A push every few minutes will be
269
+ held back, and data freshness must not hinge on one arriving.
270
+ - Finish quickly and return a result inside the short background window.
271
+
264
272
  ```swift
265
- func application(
266
- _ application: UIApplication,
267
- didReceiveRemoteNotification userInfo: [AnyHashable: Any]
268
- ) async -> UIBackgroundFetchResult {
269
- guard let updateType = userInfo["updateType"] as? String else {
270
- return .noData
271
- }
273
+ func application(_ application: UIApplication,
274
+ didReceiveRemoteNotification userInfo: [AnyHashable: Any]) async
275
+ -> UIBackgroundFetchResult {
276
+ guard let scope = userInfo["syncScope"] as? String else { return .noData }
272
277
  do {
273
- try await DataSyncService.shared.sync(trigger: updateType)
278
+ try await MailboxSync.shared.refresh(scope: scope)
274
279
  return .newData
275
280
  } catch {
276
281
  return .failed
@@ -278,223 +283,190 @@ func application(
278
283
  }
279
284
  ```
280
285
 
281
- ### Mutable Content
286
+ ### Mutable content
282
287
 
283
- Set `mutable-content: 1` plus an `alert` dictionary to let a Notification Service Extension modify an alerting remote notification before display. Silent pushes do not trigger the service extension. Use service extensions for bounded work such as downloading supported on-disk attachments, decrypting display text, or configuring communication notifications; call the content handler on every success, failure, and timeout path. For communication notifications, enable the capability, add `NSUserActivityTypes`, donate the `INInteraction`, then call `content.updating(from:)`.
288
+ Adding `mutable-content: 1` to an alerting payload hands it to your
289
+ Notification Service Extension before it is shown. Silent pushes do not start
290
+ the extension.
284
291
 
285
292
  ```json
286
293
  {
287
- "aps": {
288
- "alert": { "title": "Photo", "body": "Alice sent a photo" },
289
- "mutable-content": 1
290
- },
291
- "imageUrl": "https://example.com/photo.jpg"
294
+ "aps": {
295
+ "alert": { "title": "New photo", "body": "Ada shared a picture." },
296
+ "mutable-content": 1
297
+ },
298
+ "mediaURL": "https://media.example.com/p/77.jpg"
292
299
  }
293
300
  ```
294
301
 
295
- ### Localized Notifications
302
+ Good jobs for the extension, all kept short: fetch a supported attachment to
303
+ disk, decrypt the visible text, and turn the push into a communication
304
+ notification (capability, `NSUserActivityTypes`, `INInteraction` donation, then
305
+ `content.updating(from:)`). Every outcome, including timeout, ends with exactly
306
+ one call to the content handler. See the
307
+ [extensions guide](references/rich-notifications.md#service-extension).
296
308
 
297
- Use localization keys so the notification displays in the user's language:
309
+ ### Localized payloads
310
+
311
+ Let the device pick the language by sending keys instead of text:
298
312
 
299
313
  ```json
300
- {
301
- "aps": {
302
- "alert": {
303
- "title-loc-key": "NEW_MESSAGE_TITLE",
304
- "loc-key": "NEW_MESSAGE_BODY",
305
- "loc-args": ["Alice"]
306
- }
307
- }
308
- }
314
+ { "aps": { "alert": { "title-loc-key": "SHIPPED_TITLE", "loc-key": "SHIPPED_BODY", "loc-args": ["Thursday"] } } }
309
315
  ```
310
316
 
311
- ## Notification Handling
317
+ ## Handling Notifications
312
318
 
313
- ### UNUserNotificationCenterDelegate
319
+ ### The delegate
314
320
 
315
- Implement the delegate to control foreground display and handle user taps. Set the delegate as early as possible -- in `application(_:didFinishLaunchingWithOptions:)` or `App.init`.
321
+ Install `UNUserNotificationCenter.current().delegate` before launch finishes,
322
+ from `App.init` or from `application(_:didFinishLaunchingWithOptions:)`.
316
323
 
317
324
  ```swift
318
325
  @MainActor
319
- final class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
320
- static let shared = NotificationDelegate()
321
-
322
- // Called when notification arrives while app is in FOREGROUND
323
- func userNotificationCenter(
324
- _ center: UNUserNotificationCenter,
325
- willPresent notification: UNNotification
326
- ) async -> UNNotificationPresentationOptions {
327
- // Return which presentation elements to show
328
- // Without this, foreground notifications are silently suppressed
329
- return [.banner, .sound, .badge]
326
+ final class InboxNotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
327
+ static let shared = InboxNotificationDelegate()
328
+
329
+ nonisolated func userNotificationCenter(_ center: UNUserNotificationCenter,
330
+ willPresent notification: UNNotification) async
331
+ -> UNNotificationPresentationOptions {
332
+ [.banner, .sound, .badge]
330
333
  }
331
334
 
332
- // Called when user TAPS the notification
333
- func userNotificationCenter(
334
- _ center: UNUserNotificationCenter,
335
- didReceive response: UNNotificationResponse
336
- ) async {
337
- let userInfo = response.notification.request.content.userInfo
338
- let actionIdentifier = response.actionIdentifier
335
+ nonisolated func userNotificationCenter(_ center: UNUserNotificationCenter,
336
+ didReceive response: UNNotificationResponse) async {
337
+ let action = response.actionIdentifier
338
+ let threadID = response.notification.request.content.userInfo["threadID"] as? String
339
+ let typed = (response as? UNTextInputNotificationResponse)?.userText
340
+ await route(action: action, threadID: threadID, typed: typed)
341
+ }
339
342
 
340
- switch actionIdentifier {
343
+ private func route(action: String, threadID: String?, typed: String?) async {
344
+ switch action {
341
345
  case UNNotificationDefaultActionIdentifier:
342
- // User tapped the notification body
343
- await handleNotificationTap(userInfo: userInfo)
346
+ AppRouter.shared.openThread(threadID) // tapped the notification
344
347
  case UNNotificationDismissActionIdentifier:
345
- // User dismissed the notification
346
- break
348
+ break // swiped away
347
349
  default:
348
- // Custom action button tapped
349
- await handleCustomAction(actionIdentifier, userInfo: userInfo)
350
+ await handleChatAction(action, threadID: threadID, typed: typed)
350
351
  }
351
352
  }
352
353
  }
353
354
  ```
354
355
 
355
- ### Deep Linking from Notifications
356
+ The system does not promise to call these methods on the main thread, and
357
+ `UNNotification` and `UNNotificationResponse` are not `Sendable`. Mark the
358
+ protocol methods `nonisolated`, copy the plain values you need out of the
359
+ response (all payload data sits in `content.userInfo`), then hop to the main
360
+ actor.
356
361
 
357
- Route notification taps to the correct screen using a shared `@Observable` router. The delegate writes a pending destination; the SwiftUI view observes and consumes it.
362
+ Leave out `willPresent` and anything that arrives while your app is frontmost
363
+ is dropped without a banner.
358
364
 
359
- ```swift
360
- @Observable @MainActor
361
- final class DeepLinkRouter {
362
- static let shared = DeepLinkRouter()
365
+ ### Deep links from a tap
363
366
 
364
- var pendingDestination: AppDestination?
365
- }
366
-
367
- // In NotificationDelegate:
368
- func handleNotificationTap(userInfo: [AnyHashable: Any]) async {
369
- guard let id = userInfo["messageId"] as? String else { return }
370
- DeepLinkRouter.shared.pendingDestination = .chat(id: id)
371
- }
372
-
373
- // In SwiftUI -- observe and consume:
374
- .onChange(of: router.pendingDestination) { _, destination in
375
- if let destination {
376
- path.append(destination)
377
- router.pendingDestination = nil
378
- }
379
- }
380
- ```
367
+ Keep a single `@Observable @MainActor` router with an optional
368
+ `pendingDestination`. The delegate sets it; the root view watches it with
369
+ `.onChange(of:)`, pushes the destination onto its navigation path and then sets
370
+ it back to `nil`. A version that also switches tabs lives in the
371
+ [patterns reference](references/notification-patterns.md#routing-a-tap-to-a-screen).
381
372
 
382
- See [references/notification-patterns.md](references/notification-patterns.md) for the full deep-linking handler with tab switching.
373
+ ## Actions and Categories
383
374
 
384
- ## Notification Actions and Categories
375
+ Register categories at launch, before any notification can arrive.
385
376
 
386
- Define interactive actions that appear as buttons on the notification. Register categories at launch.
377
+ ```swift
378
+ let reply = UNTextInputNotificationAction(
379
+ identifier: "REPLY",
380
+ title: "Reply",
381
+ options: [],
382
+ textInputButtonTitle: "Send",
383
+ textInputPlaceholder: "Message"
384
+ )
385
+ let archive = UNNotificationAction(identifier: "ARCHIVE", title: "Archive", options: [.destructive])
386
+ let open = UNNotificationAction(identifier: "OPEN", title: "Open", options: [.foreground])
387
+ let approve = UNNotificationAction(identifier: "APPROVE", title: "Approve", options: [.authenticationRequired])
387
388
 
388
- ### Defining Categories and Actions
389
+ let chat = UNNotificationCategory(
390
+ identifier: "CHAT",
391
+ actions: [reply, archive],
392
+ intentIdentifiers: [],
393
+ options: [.customDismissAction] // dismissal also reaches didReceive
394
+ )
395
+ let request = UNNotificationCategory(identifier: "EXPENSE", actions: [approve, open], intentIdentifiers: [])
389
396
 
390
- ```swift
391
- func registerNotificationCategories() {
392
- let replyAction = UNTextInputNotificationAction(
393
- identifier: "REPLY_ACTION",
394
- title: "Reply",
395
- options: [],
396
- textInputButtonTitle: "Send",
397
- textInputPlaceholder: "Type a reply..."
398
- )
399
-
400
- let likeAction = UNNotificationAction(
401
- identifier: "LIKE_ACTION",
402
- title: "Like",
403
- options: []
404
- )
405
-
406
- let deleteAction = UNNotificationAction(
407
- identifier: "DELETE_ACTION",
408
- title: "Delete",
409
- options: [.destructive, .authenticationRequired]
410
- )
411
-
412
- let messageCategory = UNNotificationCategory(
413
- identifier: "MESSAGE_CATEGORY",
414
- actions: [replyAction, likeAction, deleteAction],
415
- intentIdentifiers: [],
416
- options: [.customDismissAction] // fires didReceive on dismiss too
417
- )
418
-
419
- UNUserNotificationCenter.current().setNotificationCategories([messageCategory])
420
- }
397
+ UNUserNotificationCenter.current().setNotificationCategories([chat, request])
421
398
  ```
422
399
 
423
- ### Handling Action Responses
400
+ | Option | Effect |
401
+ |--------|--------|
402
+ | `.authenticationRequired` | Runs only after the device is unlocked |
403
+ | `.destructive` | Shown in red; for delete or remove |
404
+ | `.foreground` | Tapping it opens the app |
405
+
406
+ A text action's response arrives as a `UNTextInputNotificationResponse`; its
407
+ `userText` is what the delegate above passes on as `typed`:
424
408
 
425
409
  ```swift
426
- func handleCustomAction(_ identifier: String, userInfo: [AnyHashable: Any]) async {
427
- switch identifier {
428
- case "REPLY_ACTION":
429
- // response is UNTextInputNotificationResponse for text input actions
430
- break
431
- case "LIKE_ACTION":
432
- guard let messageId = userInfo["messageId"] as? String else { return }
433
- await MessageService.shared.likeMessage(id: messageId)
434
- case "DELETE_ACTION":
435
- guard let messageId = userInfo["messageId"] as? String else { return }
436
- await MessageService.shared.deleteMessage(id: messageId)
410
+ @MainActor
411
+ func handleChatAction(_ action: String, threadID: String?, typed: String?) async {
412
+ guard let threadID else { return }
413
+ switch action {
414
+ case "REPLY":
415
+ guard let typed, !typed.isEmpty else { return }
416
+ await ChatService.shared.send(typed, to: threadID)
417
+ case "ARCHIVE":
418
+ await ChatService.shared.archive(threadID)
437
419
  default:
438
420
  break
439
421
  }
440
422
  }
441
423
  ```
442
424
 
443
- Action options:
444
- - `.authenticationRequired` -- device must be unlocked to perform the action
445
- - `.destructive` -- displayed in red; use for delete/remove actions
446
- - `.foreground` -- launches the app to the foreground when tapped
447
-
448
- ## Notification Grouping
425
+ ## Grouping
449
426
 
450
- Group related notifications with `threadIdentifier` (or `thread-id` in the APNs payload). Each unique thread becomes a separate group in Notification Center.
427
+ Notifications with the same `threadIdentifier` (local) or `thread-id` (APNs)
428
+ stack into one group; each distinct value is its own group. `summaryArgument`
429
+ and `summaryArgumentCount` on the content feed the group's summary line, and a
430
+ category can shape that line:
451
431
 
452
432
  ```swift
453
- content.threadIdentifier = "chat-alice" // all messages from Alice group together
454
- content.summaryArgument = "Alice"
455
- content.summaryArgumentCount = 3 // "3 more notifications from Alice"
456
- ```
457
-
458
- Customize the summary format string in the category:
459
-
460
- ```swift
461
- let category = UNNotificationCategory(
462
- identifier: "MESSAGE_CATEGORY",
463
- actions: [replyAction],
433
+ let chat = UNNotificationCategory(
434
+ identifier: "CHAT",
435
+ actions: [],
464
436
  intentIdentifiers: [],
465
- categorySummaryFormat: "%u more messages from %@",
437
+ hiddenPreviewsBodyPlaceholder: nil,
438
+ categorySummaryFormat: "%u new messages from %@",
466
439
  options: []
467
440
  )
468
441
  ```
469
442
 
470
443
  ## Common Mistakes
471
444
 
472
- **DON'T:** Gate APNs token registration on alert authorization when the app needs silent pushes or server token binding.
473
- **DO:** Request authorization for alerts/sounds/badges, and register with APNs whenever a device token is needed.
474
- **DON'T:** Convert device token with `String(data: deviceToken, encoding: .utf8)`.
475
- **DO:** Use hex: `deviceToken.map { String(format: "%02x", $0) }.joined()`.
476
- **DON'T:** Promise every-few-minutes silent refresh or immediate background delivery.
477
- **DO:** Say background pushes are low priority, throttled, not guaranteed, limited to a few per hour in practice, and require bounded `didReceiveRemoteNotification` work that returns the correct `UIBackgroundFetchResult`.
478
- **DON'T:** Expect a silent push to run a Notification Service Extension, or leave the extension without calling its content handler.
479
- **DO:** Use `mutable-content: 1` with an alert payload, supported on-disk attachments that the system validates and stores, `INInteraction` donation plus `content.updating(from:)` for communication notifications, and original or best-attempt content on every success, failure, and timeout path.
480
- **DON'T:** Forget foreground handling. Without `willPresent`, notifications are silently suppressed.
481
- **DO:** Implement `willPresent` and return `.banner`, `.sound`, `.badge`.
482
- **DON'T:** Set delegate too late or register from SwiftUI views without AppDelegate adaptor.
483
- **DO:** Set delegate in `App.init`; use `UIApplicationDelegateAdaptor` for APNs.
484
- **DON'T:** Upload APNs tokens only when they "change" or assume a fixed token length. **DO:** Upload on every `didRegister` callback and treat the token as opaque data converted to hex.
485
- **DON'T:** Put Live Activity, VoIP, or App Clip-specific notification rules here. **DO:** Route those to `activitykit`, `callkit`, and `app-clips`.
445
+ | Mistake | Instead |
446
+ |---------|---------|
447
+ | Calling `registerForRemoteNotifications()` only after `.authorized` | Register anyway; silent pushes and server token binding need it |
448
+ | `String(data: token, encoding: .utf8)` | Hex-encode each byte |
449
+ | Promising frequent silent refreshes | Expect a few per hour at most |
450
+ | Expecting a silent push to run the service extension | Only `mutable-content: 1` alert pushes do |
451
+ | A service-extension path that never calls the handler | Call it once on every path, timeout included |
452
+ | No `willPresent` | Implement it or foreground notifications vanish |
453
+ | Setting the delegate late, or registering from a view without the adaptor | Set it in `App.init`, use `@UIApplicationDelegateAdaptor` |
454
+ | Uploading the token only when it "changes" | Treat it as opaque hex and upload on every callback |
455
+ | Mixing in rules for Live Activities, VoIP calls or App Clips | Hand those to `live-activities`, `callkit-voip`, `app-clips` |
486
456
 
487
457
  ## Review Checklist
488
458
 
489
- - [ ] Authorization requested before visible alerts/sounds/badges; denied case handled (Settings link)
490
- - [ ] APNs registration not incorrectly blocked by alert authorization status
491
- - [ ] Device token converted to hex, uploaded on every callback, and not treated as a locally cached or fixed-length constant
492
- - [ ] `UNUserNotificationCenterDelegate` set in `App.init` or `application(_:didFinishLaunching:)`
493
- - [ ] Foreground (`willPresent`) and tap (`didReceive`) handling implemented
494
- - [ ] Categories/actions registered at launch if interactive notifications needed
495
- - [ ] Silent push uses `content-available: 1`, no alert/sound/badge, `apns-push-type: background`, `apns-priority: 5`, Background Modes > Remote notifications, throttling caveats, and correct `UIBackgroundFetchResult`
496
-
497
- ## References
498
- - [references/notification-patterns.md](references/notification-patterns.md) - AppDelegate setup, APNs callbacks, deep-link router, silent push, debugging
499
- - [references/rich-notifications.md](references/rich-notifications.md) - Service Extension, Content Extension, attachments, communication notifications
500
- - Apple docs: [APNs registration](https://sosumi.ai/documentation/usernotifications/registering-your-app-with-apns), [permission](https://sosumi.ai/documentation/usernotifications/asking-permission-to-use-notifications), [payloads](https://sosumi.ai/documentation/usernotifications/generating-a-remote-notification), [background pushes](https://sosumi.ai/documentation/usernotifications/pushing-background-updates-to-your-app)
459
+ - [ ] Permission is requested before any alert, sound or badge, and a denied user gets a link to Settings
460
+ - [ ] APNs registration does not depend on alert authorization
461
+ - [ ] Token is hex-encoded, uploaded on every callback, not cached as truth, no fixed length
462
+ - [ ] The center delegate is installed from `App.init` or `application(_:didFinishLaunchingWithOptions:)`
463
+ - [ ] Both `willPresent` and `didReceive` are implemented
464
+ - [ ] Categories and actions are registered at launch when notifications are interactive
465
+ - [ ] Silent pushes carry only `content-available: 1` (nothing that alerts), use the `background` push type at priority 5, rely on the Remote notifications mode, accept throttling, and report an honest `UIBackgroundFetchResult`
466
+
467
+ ## Apple Documentation
468
+
469
+ - [APNs registration](https://developer.apple.com/documentation/usernotifications/registering-your-app-with-apns)
470
+ - [Requesting permission](https://developer.apple.com/documentation/usernotifications/asking-permission-to-use-notifications)
471
+ - [Building a remote payload](https://developer.apple.com/documentation/usernotifications/generating-a-remote-notification)
472
+ - [Background updates by push](https://developer.apple.com/documentation/usernotifications/pushing-background-updates-to-your-app)