@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,498 +1,348 @@
1
1
  ---
2
2
  name: core-bluetooth
3
- description: "Build direct Bluetooth Low Energy workflows with Core Bluetooth. Use when implementing BLE central or peripheral GATT communication, scanning or connecting with CBCentralManager, discovering services and characteristics, reading/writing/subscribing with CBPeripheral, publishing local services with CBPeripheralManager, handling Bluetooth authorization, background BLE modes, state restoration, write flow control, or CBUUID-based workflows. For privacy-preserving accessory setup/picker flows, use accessorysetupkit first and return here for post-setup GATT communication."
3
+ description: "Direct BLE with Core Bluetooth: central and peripheral GATT communication, CBCentralManager scanning and connecting, service and characteristic discovery, CBPeripheral reads, writes and subscriptions, CBPeripheralManager local services, Bluetooth authorization, background BLE modes, state restoration, write flow control, CBUUID workflows. Use when an app talks to BLE accessories or acts as one. Not for privacy-preserving accessory setup or picker flows: AccessorySetupKit first, then GATT here."
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
  # Core Bluetooth
9
9
 
10
- Scan for, connect to, and exchange data with Bluetooth Low Energy (BLE) devices.
11
- Covers the central role (scanning and connecting to peripherals), the peripheral
12
- role (advertising services), background modes, and state restoration.
13
- Targets Swift 6.3 / iOS 26+.
14
- Use `accessorysetupkit` for privacy-preserving accessory discovery and setup;
15
- use this skill for direct Core Bluetooth GATT communication.
16
-
17
- ## Contents
18
-
19
- - [Setup](#setup)
20
- - [Central Role: Scanning](#central-role-scanning)
21
- - [Central Role: Connecting](#central-role-connecting)
22
- - [Discovering Services and Characteristics](#discovering-services-and-characteristics)
23
- - [Reading, Writing, and Notifications](#reading-writing-and-notifications)
24
- - [Peripheral Role: Advertising](#peripheral-role-advertising)
25
- - [Background BLE](#background-ble)
26
- - [State Restoration](#state-restoration)
27
- - [Common Mistakes](#common-mistakes)
28
- - [Review Checklist](#review-checklist)
29
- - [References](#references)
10
+ Core Bluetooth lets an app act as a BLE **central** (scan, connect, read and
11
+ write another device) or as a **peripheral** (publish services others connect
12
+ to). This skill covers both roles, background modes and state restoration, for
13
+ Swift 6.3 and iOS 26.
14
+
15
+ Finding and pairing an accessory through the system picker, without broad
16
+ Bluetooth permission, is AccessorySetupKit's job. Once the user has picked the
17
+ accessory, the GATT conversation (services, characteristics, reads, writes,
18
+ notifications) happens here.
30
19
 
31
20
  ## Setup
32
21
 
33
- ### Info.plist Keys
22
+ ### Info.plist
34
23
 
35
24
  | Key | Purpose |
36
- |---|---|
37
- | `NSBluetoothAlwaysUsageDescription` | Required. Explains why the app uses Bluetooth |
38
- | `UIBackgroundModes` with `bluetooth-central` | Background scanning and connecting |
39
- | `UIBackgroundModes` with `bluetooth-peripheral` | Background advertising |
25
+ |-----|---------|
26
+ | `NSBluetoothAlwaysUsageDescription` | Required. Tells the user why the app uses Bluetooth |
27
+ | `UIBackgroundModes` with `bluetooth-central` | Scan and stay connected in the background |
28
+ | `UIBackgroundModes` with `bluetooth-peripheral` | Advertise and serve in the background |
40
29
 
41
- ### Bluetooth Authorization
30
+ ### Permission and power state
42
31
 
43
- Core Bluetooth has no explicit permission request API. Add
44
- `NSBluetoothAlwaysUsageDescription`, create the manager when the app is ready for
45
- Bluetooth access, then check `manager.authorization` and `manager.state`.
46
- Treat `.denied` and `.restricted` as terminal until the user changes Settings;
47
- wait for `.poweredOn` before scanning, connecting, advertising, or publishing
48
- services.
32
+ There is no "request Bluetooth permission" call. The system asks the first
33
+ time the app creates a manager, so create one only when the user reaches the
34
+ feature that needs it. Then look at two things:
49
35
 
50
- ## Central Role: Scanning
36
+ - `manager.authorization`: `.denied` and `.restricted` stay that way until the
37
+ user changes Settings, so show guidance rather than retrying.
38
+ - `manager.state`: do nothing (scan, connect, advertise, add services) until it
39
+ is `.poweredOn`.
51
40
 
52
- ### Creating the Central Manager
41
+ ## Central role
53
42
 
54
- Always wait for the `poweredOn` state before scanning.
43
+ ### Scanning
55
44
 
56
45
  ```swift
57
46
  import CoreBluetooth
58
47
 
59
- final class BluetoothManager: NSObject, CBCentralManagerDelegate {
60
- private var centralManager: CBCentralManager!
61
- private var discoveredPeripheral: CBPeripheral?
48
+ final class ScaleLink: NSObject, CBCentralManagerDelegate {
49
+ static var weightService: CBUUID { CBUUID(string: "181D") }
50
+
51
+ private var central: CBCentralManager!
52
+ private var scale: CBPeripheral?
62
53
 
63
54
  override init() {
64
55
  super.init()
65
- centralManager = CBCentralManager(delegate: self, queue: nil)
56
+ central = CBCentralManager(delegate: self, queue: nil)
66
57
  }
67
58
 
68
59
  func centralManagerDidUpdateState(_ central: CBCentralManager) {
69
60
  guard central.state == .poweredOn else { return }
70
- startScanning()
61
+ central.scanForPeripherals(
62
+ withServices: [Self.weightService],
63
+ options: [CBCentralManagerScanOptionAllowDuplicatesKey: false]
64
+ )
65
+ }
66
+
67
+ func centralManager(_ central: CBCentralManager, didDiscover found: CBPeripheral,
68
+ advertisementData: [String: Any], rssi: NSNumber) {
69
+ guard rssi.intValue > -75 else { return }
70
+ scale = found
71
+ central.stopScan()
72
+ central.connect(found)
71
73
  }
72
74
  }
73
75
  ```
74
76
 
75
- ### Scanning for Peripherals
77
+ - Scan for the service UUIDs you care about. `withServices: nil` finds
78
+ everything, burns battery and has no place in a shipping app.
79
+ - Ignoring weak signals (here anything at or below -75 dBm) is optional.
80
+ - Keep a strong reference to the discovered `CBPeripheral`. Core Bluetooth
81
+ does not, and a released peripheral silently never connects.
82
+ - Stop scanning as soon as the target turns up, then connect.
76
83
 
77
- Scan for specific service UUIDs to save power. Pass `nil` to discover all
78
- peripherals (not recommended in production).
84
+ The implicitly unwrapped `central` exists only because `self` must be fully
85
+ initialized before it can be the delegate. `CBUUID` is not `Sendable`, and
86
+ Swift 6 rejects a stored `static let` of it on a type without actor isolation,
87
+ so the UUIDs are computed static properties. A `@MainActor` type can keep
88
+ stored ones, as the SwiftUI monitor in the reference does.
79
89
 
80
- ```swift
81
- let heartRateServiceUUID = CBUUID(string: "180D")
82
-
83
- func startScanning() {
84
- centralManager.scanForPeripherals(
85
- withServices: [heartRateServiceUUID],
86
- options: [CBCentralManagerScanOptionAllowDuplicatesKey: false]
87
- )
88
- }
89
-
90
- func centralManager(
91
- _ central: CBCentralManager,
92
- didDiscover peripheral: CBPeripheral,
93
- advertisementData: [String: Any],
94
- rssi RSSI: NSNumber
95
- ) {
96
- guard RSSI.intValue > -70 else { return } // Filter weak signals
97
-
98
- // IMPORTANT: Retain the peripheral -- it will be deallocated otherwise
99
- discoveredPeripheral = peripheral
100
- centralManager.stopScan()
101
- centralManager.connect(peripheral, options: nil)
102
- }
103
- ```
104
-
105
- ## Central Role: Connecting
90
+ ### Connecting and disconnecting
106
91
 
107
92
  ```swift
108
- func centralManager(
109
- _ central: CBCentralManager,
110
- didConnect peripheral: CBPeripheral
111
- ) {
112
- peripheral.delegate = self
113
- peripheral.discoverServices([heartRateServiceUUID])
114
- }
93
+ extension ScaleLink {
94
+ func centralManager(_ central: CBCentralManager, didConnect device: CBPeripheral) {
95
+ device.delegate = self
96
+ device.discoverServices([Self.weightService])
97
+ }
115
98
 
116
- func centralManager(
117
- _ central: CBCentralManager,
118
- didDisconnectPeripheral peripheral: CBPeripheral,
119
- timestamp: CFAbsoluteTime,
120
- isReconnecting: Bool,
121
- error: Error?
122
- ) {
123
- if isReconnecting {
124
- // System is automatically reconnecting
125
- return
99
+ func centralManager(_ central: CBCentralManager, didDisconnectPeripheral device: CBPeripheral,
100
+ timestamp: CFAbsoluteTime, isReconnecting: Bool, error: (any Error)?) {
101
+ if isReconnecting { return }
102
+ scale = nil
126
103
  }
127
- // Handle disconnection -- optionally reconnect
128
- discoveredPeripheral = nil
129
104
  }
130
105
  ```
131
106
 
132
- ## Discovering Services and Characteristics
107
+ Set the peripheral's delegate before asking it for anything. When
108
+ `isReconnecting` is true the system is already reconnecting on your behalf;
109
+ otherwise decide whether to reconnect yourself and release the reference.
133
110
 
134
- Implement `CBPeripheralDelegate` to walk the service/characteristic tree.
111
+ ## Discovering services and characteristics
135
112
 
136
113
  ```swift
137
- extension BluetoothManager: CBPeripheralDelegate {
138
- func peripheral(
139
- _ peripheral: CBPeripheral,
140
- didDiscoverServices error: Error?
141
- ) {
142
- guard let services = peripheral.services else { return }
143
- for service in services {
144
- peripheral.discoverCharacteristics(nil, for: service)
114
+ extension ScaleLink: CBPeripheralDelegate {
115
+ static var weightMeasurement: CBUUID { CBUUID(string: "2A9D") }
116
+
117
+ func peripheral(_ device: CBPeripheral, didDiscoverServices error: (any Error)?) {
118
+ for service in device.services ?? [] {
119
+ device.discoverCharacteristics([Self.weightMeasurement, CBUUID(string: "2A19")], for: service)
145
120
  }
146
121
  }
147
122
 
148
- func peripheral(
149
- _ peripheral: CBPeripheral,
150
- didDiscoverCharacteristicsFor service: CBService,
151
- error: Error?
152
- ) {
153
- guard let characteristics = service.characteristics else { return }
154
- for characteristic in characteristics {
155
- if characteristic.properties.contains(.notify) {
156
- peripheral.setNotifyValue(true, for: characteristic)
123
+ func peripheral(_ device: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: (any Error)?) {
124
+ for trait in service.characteristics ?? [] {
125
+ if trait.properties.contains(.notify) {
126
+ device.setNotifyValue(true, for: trait)
157
127
  }
158
- if characteristic.properties.contains(.read) {
159
- peripheral.readValue(for: characteristic)
128
+ if trait.properties.contains(.read) {
129
+ device.readValue(for: trait)
160
130
  }
161
131
  }
162
132
  }
163
133
  }
164
134
  ```
165
135
 
166
- ## Reading, Writing, and Notifications
136
+ Check `properties` before each read, write or subscription.
167
137
 
168
- ### Reading a Value
138
+ ## Reading, writing and notifications
139
+
140
+ Reads and notifications arrive in the same callback:
169
141
 
170
142
  ```swift
171
- func peripheral(
172
- _ peripheral: CBPeripheral,
173
- didUpdateValueFor characteristic: CBCharacteristic,
174
- error: Error?
175
- ) {
176
- guard let data = characteristic.value else { return }
177
-
178
- switch characteristic.uuid {
143
+ func peripheral(_ device: CBPeripheral, didUpdateValueFor trait: CBCharacteristic, error: (any Error)?) {
144
+ guard error == nil, let bytes = trait.value else { return }
145
+ switch trait.uuid {
179
146
  case CBUUID(string: "2A37"):
180
- if let heartRate = parseHeartRate(data) {
181
- print("Heart rate: \(heartRate) bpm")
182
- }
147
+ pulse = beatsPerMinute(bytes)
183
148
  case CBUUID(string: "2A19"):
184
- let batteryLevel = data.first.map { Int($0) } ?? 0
185
- print("Battery: \(batteryLevel)%")
149
+ batteryPercent = bytes.first.map(Int.init)
186
150
  default:
187
151
  break
188
152
  }
189
153
  }
154
+ ```
190
155
 
191
- private func parseHeartRate(_ data: Data) -> Int? {
192
- guard data.count >= 2 else { return nil }
193
- let flags = data[0]
194
- let is16Bit = (flags & 0x01) != 0
195
- if is16Bit {
196
- guard data.count >= 3 else { return nil }
197
- return Int(data[1]) | (Int(data[2]) << 8)
198
- } else {
199
- return Int(data[1])
156
+ Heart Rate Measurement (`2A37`) needs care with its flags byte:
157
+
158
+ ```swift
159
+ func beatsPerMinute(_ bytes: Data) -> Int? {
160
+ guard bytes.count >= 2 else { return nil }
161
+ let wide = bytes[bytes.startIndex] & 0x01 != 0
162
+ if wide {
163
+ guard bytes.count >= 3 else { return nil }
164
+ let low = UInt16(bytes[bytes.startIndex + 1])
165
+ let high = UInt16(bytes[bytes.startIndex + 2])
166
+ return Int(low | high << 8)
200
167
  }
168
+ return Int(bytes[bytes.startIndex + 1])
201
169
  }
202
170
  ```
203
171
 
204
- ### Writing a Value
172
+ Bit 0 set means a 16-bit little-endian value in bytes 1 and 2; otherwise the
173
+ value is the single byte 1.
174
+
175
+ ### Choosing a write type
205
176
 
206
177
  ```swift
207
- func writeValue(_ data: Data, to characteristic: CBCharacteristic,
208
- on peripheral: CBPeripheral,
209
- preferResponse: Bool = true) {
178
+ func send(_ payload: Data, to trait: CBCharacteristic, on device: CBPeripheral, confirm: Bool) -> Bool {
210
179
  let type: CBCharacteristicWriteType
211
- if preferResponse, characteristic.properties.contains(.write) {
180
+ if confirm, trait.properties.contains(.write) {
212
181
  type = .withResponse
213
- } else if characteristic.properties.contains(.writeWithoutResponse),
214
- peripheral.canSendWriteWithoutResponse {
182
+ } else if trait.properties.contains(.writeWithoutResponse), device.canSendWriteWithoutResponse {
215
183
  type = .withoutResponse
216
- } else if characteristic.properties.contains(.write) {
184
+ } else if trait.properties.contains(.write) {
217
185
  type = .withResponse
218
186
  } else {
219
- return
220
- }
221
-
222
- guard data.count <= peripheral.maximumWriteValueLength(for: type) else { return }
223
- peripheral.writeValue(data, for: characteristic, type: type)
224
- }
225
-
226
- // Confirmation callback for .withResponse writes.
227
- func peripheral(
228
- _ peripheral: CBPeripheral,
229
- didWriteValueFor characteristic: CBCharacteristic,
230
- error: Error?
231
- ) {
232
- if let error {
233
- print("Write failed: \(error.localizedDescription)")
187
+ return false
234
188
  }
189
+ guard payload.count <= device.maximumWriteValueLength(for: type) else { return false }
190
+ device.writeValue(payload, for: trait, type: type)
191
+ return true
235
192
  }
236
-
237
- // Resume queued .withoutResponse writes here.
238
- func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) {}
239
193
  ```
240
194
 
241
- ### Subscribing to Notifications
195
+ - `.withResponse` writes are confirmed in `peripheral(_:didWriteValueFor:error:)`.
196
+ - When `canSendWriteWithoutResponse` is false, queue the data and resume in
197
+ `peripheralIsReady(toSendWriteWithoutResponse:)`.
198
+ - Payloads longer than `maximumWriteValueLength(for:)` must be split.
242
199
 
243
- ```swift
244
- // Subscribe
245
- peripheral.setNotifyValue(true, for: characteristic)
246
-
247
- // Unsubscribe
248
- peripheral.setNotifyValue(false, for: characteristic)
249
-
250
- // Confirmation
251
- func peripheral(
252
- _ peripheral: CBPeripheral,
253
- didUpdateNotificationStateFor characteristic: CBCharacteristic,
254
- error: Error?
255
- ) {
256
- if characteristic.isNotifying {
257
- print("Now receiving notifications for \(characteristic.uuid)")
258
- }
259
- }
260
- ```
200
+ ### Subscriptions
261
201
 
262
- ## Peripheral Role: Advertising
202
+ `setNotifyValue(true, for:)` subscribes and `setNotifyValue(false, for:)`
203
+ unsubscribes. The outcome arrives in
204
+ `peripheral(_:didUpdateNotificationStateFor:error:)`, where
205
+ `characteristic.isNotifying` tells you the current state.
263
206
 
264
- Publish services from the local device using `CBPeripheralManager`.
207
+ ## Peripheral role
265
208
 
266
209
  ```swift
267
- final class BLEPeripheralManager: NSObject, CBPeripheralManagerDelegate {
268
- private var peripheralManager: CBPeripheralManager!
269
- private let serviceUUID = CBUUID(string: "12345678-1234-1234-1234-123456789ABC")
270
- private let charUUID = CBUUID(string: "12345678-1234-1234-1234-123456789ABD")
210
+ final class ThermometerBeacon: NSObject, CBPeripheralManagerDelegate {
211
+ static var service: CBUUID { CBUUID(string: "5E1A7D30-2C4B-4F55-9B0E-7A1C3D2E8F10") }
212
+ static var reading: CBUUID { CBUUID(string: "5E1A7D31-2C4B-4F55-9B0E-7A1C3D2E8F10") }
213
+
214
+ private var manager: CBPeripheralManager!
271
215
 
272
216
  override init() {
273
217
  super.init()
274
- peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
275
- }
276
-
277
- func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
278
- guard peripheral.state == .poweredOn else { return }
279
- setupService()
218
+ manager = CBPeripheralManager(delegate: self, queue: nil)
280
219
  }
281
220
 
282
- private func setupService() {
283
- let characteristic = CBMutableCharacteristic(
284
- type: charUUID,
221
+ func peripheralManagerDidUpdateState(_ manager: CBPeripheralManager) {
222
+ guard manager.state == .poweredOn else { return }
223
+ let reading = CBMutableCharacteristic(
224
+ type: Self.reading,
285
225
  properties: [.read, .notify],
286
226
  value: nil,
287
227
  permissions: [.readable]
288
228
  )
289
-
290
- let service = CBMutableService(type: serviceUUID, primary: true)
291
- service.characteristics = [characteristic]
292
- peripheralManager.add(service)
229
+ let service = CBMutableService(type: Self.service, primary: true)
230
+ service.characteristics = [reading]
231
+ manager.add(service)
293
232
  }
294
233
 
295
- func peripheralManager(
296
- _ peripheral: CBPeripheralManager,
297
- didAdd service: CBService,
298
- error: Error?
299
- ) {
234
+ func peripheralManager(_ manager: CBPeripheralManager, didAdd service: CBService, error: (any Error)?) {
300
235
  guard error == nil else { return }
301
- peripheralManager.startAdvertising([
302
- CBAdvertisementDataServiceUUIDsKey: [serviceUUID],
303
- CBAdvertisementDataLocalNameKey: "MyDevice"
236
+ manager.startAdvertising([
237
+ CBAdvertisementDataServiceUUIDsKey: [Self.service],
238
+ CBAdvertisementDataLocalNameKey: "Porch Thermometer",
304
239
  ])
305
240
  }
306
241
  }
307
242
  ```
308
243
 
309
- ## Background BLE
310
-
311
- ### Background Central Mode
244
+ Custom services use full 128-bit UUIDs created with `CBUUID(string:)`. Leaving
245
+ `value` nil makes the characteristic dynamic, so reads reach your delegate.
246
+ Answering read, write and subscription requests is shown in the
247
+ [patterns reference](references/ble-patterns.md).
312
248
 
313
- Add `bluetooth-central` to `UIBackgroundModes`. In the background:
249
+ ## Background operation
314
250
 
315
- - Scanning must specify one or more service UUIDs; `nil` scans are foreground-only
316
- - Scan options, including `CBCentralManagerScanOptionAllowDuplicatesKey`, have no effect
251
+ **Central (`bluetooth-central`):**
317
252
 
318
- ### Background Peripheral Mode
253
+ - Background scans must name service UUIDs; a `nil` scan runs only in the
254
+ foreground.
255
+ - Scan options, `CBCentralManagerScanOptionAllowDuplicatesKey` included, are
256
+ ignored in the background.
319
257
 
320
- Add `bluetooth-peripheral` to `UIBackgroundModes`. In the background:
258
+ **Peripheral (`bluetooth-peripheral`):**
321
259
 
322
- - Without this mode, published service contents are disabled while suspended
323
- - The local name is not advertised
324
- - Service UUIDs move to the overflow area and require explicit service scans
260
+ - Without this mode, the contents of published services are unavailable while
261
+ the app is suspended.
262
+ - In the background the local name is not advertised.
263
+ - Service UUIDs move to an overflow area that only centrals scanning
264
+ explicitly for those services can see.
325
265
 
326
- ## State Restoration
266
+ ## State restoration
327
267
 
328
- State restoration allows the system to re-create your central or peripheral
329
- manager after your app is terminated and relaunched for a BLE event.
330
-
331
- ### Central Manager State Restoration
268
+ If the app is terminated, the system can relaunch it for a Bluetooth event and
269
+ hand back the manager it had. Opt in with a restoration identifier:
332
270
 
333
271
  ```swift
334
- // 1. Create with a restoration identifier
335
- centralManager = CBCentralManager(
272
+ central = CBCentralManager(
336
273
  delegate: self,
337
274
  queue: nil,
338
- options: [CBCentralManagerOptionRestoreIdentifierKey: "myCentral"]
275
+ options: [CBCentralManagerOptionRestoreIdentifierKey: "scale-central"]
339
276
  )
340
277
 
341
- // 2. Implement the restoration delegate method
342
- func centralManager(
343
- _ central: CBCentralManager,
344
- willRestoreState dict: [String: Any]
345
- ) {
346
- if let peripherals = dict[CBCentralManagerRestoredStatePeripheralsKey]
347
- as? [CBPeripheral] {
348
- for peripheral in peripherals {
349
- // Re-assign delegate and retain
350
- peripheral.delegate = self
351
- discoveredPeripheral = peripheral
278
+ func centralManager(_ central: CBCentralManager, willRestoreState state: [String: Any]) {
279
+ if let devices = state[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] {
280
+ for device in devices {
281
+ device.delegate = self
282
+ scale = device
352
283
  }
353
284
  }
354
- let restoredServices = dict[CBCentralManagerRestoredStateScanServicesKey]
355
- as? [CBUUID]
356
- let restoredOptions = dict[CBCentralManagerRestoredStateScanOptionsKey]
357
- as? [String: Any]
358
- // Resume scanning with restoredServices/restoredOptions if still needed.
359
- }
360
- ```
361
-
362
- ### Peripheral Manager State Restoration
363
-
364
- ```swift
365
- peripheralManager = CBPeripheralManager(
366
- delegate: self,
367
- queue: nil,
368
- options: [CBPeripheralManagerOptionRestoreIdentifierKey: "myPeripheral"]
369
- )
370
-
371
- func peripheralManager(
372
- _ peripheral: CBPeripheralManager,
373
- willRestoreState dict: [String: Any]
374
- ) {
375
- let services = dict[CBPeripheralManagerRestoredStateServicesKey]
376
- as? [CBMutableService]
377
- let advertisement = dict[CBPeripheralManagerRestoredStateAdvertisementDataKey]
378
- as? [String: Any]
379
- // Reconnect app state to restored services/advertisement as needed.
380
- }
381
- ```
382
-
383
- ## Common Mistakes
384
-
385
- ### DON'T: Scan or connect before poweredOn
386
-
387
- ```swift
388
- // WRONG: Scanning immediately -- manager may not be ready
389
- let manager = CBCentralManager(delegate: self, queue: nil)
390
- manager.scanForPeripherals(withServices: nil) // May silently fail
391
-
392
- // CORRECT: Wait for poweredOn in the delegate
393
- func centralManagerDidUpdateState(_ central: CBCentralManager) {
394
- if central.state == .poweredOn {
395
- central.scanForPeripherals(withServices: [serviceUUID])
396
- }
397
- }
398
- ```
399
-
400
- ### DON'T: Lose the peripheral reference
401
-
402
- Core Bluetooth does not retain discovered peripherals. If you don't hold a
403
- strong reference, the peripheral is deallocated and the connection fails silently.
404
-
405
- ```swift
406
- // WRONG: No strong reference kept
407
- func centralManager(_ central: CBCentralManager,
408
- didDiscover peripheral: CBPeripheral, ...) {
409
- central.connect(peripheral) // peripheral may be deallocated
410
- }
411
-
412
- // CORRECT: Retain the peripheral
413
- func centralManager(_ central: CBCentralManager,
414
- didDiscover peripheral: CBPeripheral, ...) {
415
- self.discoveredPeripheral = peripheral // Strong reference
416
- central.connect(peripheral)
417
- }
418
- ```
419
-
420
- ### DON'T: Scan for nil services in production
421
-
422
- ```swift
423
- // WRONG: Discovers every BLE device in range -- drains battery
424
- centralManager.scanForPeripherals(withServices: nil)
425
-
426
- // CORRECT: Specify the service UUIDs you need
427
- centralManager.scanForPeripherals(withServices: [targetServiceUUID])
428
- ```
429
-
430
- ### DON'T: Assume connection order or timing
431
-
432
- ```swift
433
- // WRONG: Assuming immediate connection
434
- centralManager.connect(peripheral)
435
- discoverServicesNow() // Peripheral not connected yet
436
-
437
- // CORRECT: Discover services in the didConnect callback
438
- func centralManager(_ central: CBCentralManager,
439
- didConnect peripheral: CBPeripheral) {
440
- peripheral.delegate = self
441
- peripheral.discoverServices([serviceUUID])
442
- }
443
- ```
444
-
445
- ### DON'T: Write without checking properties and flow control
446
-
447
- ```swift
448
- // WRONG: May fail, report an error, or provide no confirmation
449
- peripheral.writeValue(data, for: characteristic, type: .withResponse)
450
-
451
- // CORRECT: Check properties, length, and .withoutResponse flow control
452
- if characteristic.properties.contains(.write),
453
- data.count <= peripheral.maximumWriteValueLength(for: .withResponse) {
454
- peripheral.writeValue(data, for: characteristic, type: .withResponse)
455
- } else if characteristic.properties.contains(.writeWithoutResponse),
456
- peripheral.canSendWriteWithoutResponse,
457
- data.count <= peripheral.maximumWriteValueLength(for: .withoutResponse) {
458
- peripheral.writeValue(data, for: characteristic, type: .withoutResponse)
285
+ let services = state[CBCentralManagerRestoredStateScanServicesKey] as? [CBUUID]
286
+ let options = state[CBCentralManagerRestoredStateScanOptionsKey] as? [String: Any]
287
+ resumeScanIfNeeded(services: services, options: options)
459
288
  }
460
289
  ```
461
290
 
462
- ## Review Checklist
463
-
464
- - [ ] `NSBluetoothAlwaysUsageDescription` added to Info.plist
465
- - [ ] All BLE operations gated on `centralManagerDidUpdateState` returning `.poweredOn`
466
- - [ ] Discovered peripherals retained with a strong reference
467
- - [ ] Scanning uses specific service UUIDs (not `nil`) in production
468
- - [ ] `CBPeripheralDelegate` set before calling `discoverServices`
469
- - [ ] Characteristic properties checked before read/write/notify
470
- - [ ] Write payloads stay within `maximumWriteValueLength(for:)`
471
- - [ ] `.withoutResponse` writes honor `canSendWriteWithoutResponse`
472
- - [ ] Background mode (`bluetooth-central` or `bluetooth-peripheral`) added if needed
473
- - [ ] State restoration identifier set if app needs relaunch-on-BLE-event support
474
- - [ ] `willRestoreState` delegate method implemented when using state restoration
475
- - [ ] Scanning stopped after discovering the target peripheral
476
- - [ ] Disconnection handled with optional automatic reconnect logic
477
- - [ ] Write type matches characteristic properties (`.withResponse` vs `.withoutResponse`)
291
+ A peripheral manager does the same with
292
+ `CBPeripheralManagerOptionRestoreIdentifierKey`, and its
293
+ `peripheralManager(_:willRestoreState:)` reads
294
+ `CBPeripheralManagerRestoredStateServicesKey` (an array of `CBMutableService`)
295
+ and `CBPeripheralManagerRestoredStateAdvertisementDataKey`.
296
+
297
+ ## Common mistakes
298
+
299
+ - **Acting before `.poweredOn`.** Scans and connects can fail without a word.
300
+ Start them from the state callback.
301
+ - **Dropping the peripheral.** Nothing else retains it, and the connection
302
+ quietly never happens.
303
+ - **`nil` service scans in production.** Heavy on battery; list the UUIDs.
304
+ - **Assuming the connection is instant.** Start service discovery in
305
+ `didConnect`, not right after `connect`.
306
+ - **Writing blind.** Ignoring properties, the maximum write length or
307
+ `.withoutResponse` flow control leads to failed or unconfirmed writes.
308
+
309
+ ## Review checklist
310
+
311
+ - [ ] `NSBluetoothAlwaysUsageDescription` is present
312
+ - [ ] Every operation waits for `.poweredOn`
313
+ - [ ] Discovered peripherals are held strongly
314
+ - [ ] Production scans list specific service UUIDs
315
+ - [ ] `CBPeripheralDelegate` is set before `discoverServices`
316
+ - [ ] Properties are checked before read, write or notify
317
+ - [ ] Writes fit within `maximumWriteValueLength(for:)`
318
+ - [ ] `.withoutResponse` writes respect `canSendWriteWithoutResponse`
319
+ - [ ] Needed background modes are declared
320
+ - [ ] A restoration identifier is set when relaunch on BLE events matters
321
+ - [ ] `willRestoreState` is implemented whenever restoration is on
322
+ - [ ] Scanning stops once the target is found
323
+ - [ ] Disconnects are handled, with reconnection where it makes sense
324
+ - [ ] Write type matches the characteristic's properties
478
325
 
479
326
  ## References
480
327
 
481
- - Extended patterns (reconnection strategies, data parsing, SwiftUI integration): [references/ble-patterns.md](references/ble-patterns.md)
482
- - [Core Bluetooth framework](https://sosumi.ai/documentation/corebluetooth)
483
- - [CBCentralManager](https://sosumi.ai/documentation/corebluetooth/cbcentralmanager)
484
- - [CBPeripheral](https://sosumi.ai/documentation/corebluetooth/cbperipheral)
485
- - [CBPeripheralManager](https://sosumi.ai/documentation/corebluetooth/cbperipheralmanager)
486
- - [CBService](https://sosumi.ai/documentation/corebluetooth/cbservice)
487
- - [CBCharacteristic](https://sosumi.ai/documentation/corebluetooth/cbcharacteristic)
488
- - [CBUUID](https://sosumi.ai/documentation/corebluetooth/cbuuid)
489
- - [CBCentralManagerDelegate](https://sosumi.ai/documentation/corebluetooth/cbcentralmanagerdelegate)
490
- - [CBPeripheralDelegate](https://sosumi.ai/documentation/corebluetooth/cbperipheraldelegate)
491
- - [NSBluetoothAlwaysUsageDescription](https://sosumi.ai/documentation/bundleresources/information-property-list/nsbluetoothalwaysusagedescription)
492
- - [CBManagerAuthorization](https://sosumi.ai/documentation/corebluetooth/cbmanagerauthorization)
493
- - [scanForPeripherals(withServices:options:)](https://sosumi.ai/documentation/corebluetooth/cbcentralmanager/scanforperipherals(withservices:options:))
494
- - [startAdvertising(_:)](https://sosumi.ai/documentation/corebluetooth/cbperipheralmanager/startadvertising(_:))
495
- - [writeValue(_:for:type:)](https://sosumi.ai/documentation/corebluetooth/cbperipheral/writevalue(_:for:type:))
496
- - [maximumWriteValueLength(for:)](https://sosumi.ai/documentation/corebluetooth/cbperipheral/maximumwritevaluelength(for:))
497
- - [canSendWriteWithoutResponse](https://sosumi.ai/documentation/corebluetooth/cbperipheral/cansendwritewithoutresponse)
498
- - [Configuring background execution modes](https://sosumi.ai/documentation/xcode/configuring-background-execution-modes)
328
+ - [BLE patterns](references/ble-patterns.md): SwiftUI manager and view,
329
+ reconnection, byte parsing, write flow control, several peripherals, L2CAP,
330
+ answering requests as a peripheral
331
+ - Apple documentation (developer.apple.com):
332
+ [framework](https://developer.apple.com/documentation/corebluetooth),
333
+ [central manager](https://developer.apple.com/documentation/corebluetooth/cbcentralmanager),
334
+ [peripheral](https://developer.apple.com/documentation/corebluetooth/cbperipheral),
335
+ [peripheral manager](https://developer.apple.com/documentation/corebluetooth/cbperipheralmanager),
336
+ [service](https://developer.apple.com/documentation/corebluetooth/cbservice),
337
+ [characteristic](https://developer.apple.com/documentation/corebluetooth/cbcharacteristic),
338
+ [UUID](https://developer.apple.com/documentation/corebluetooth/cbuuid),
339
+ [central delegate](https://developer.apple.com/documentation/corebluetooth/cbcentralmanagerdelegate),
340
+ [peripheral delegate](https://developer.apple.com/documentation/corebluetooth/cbperipheraldelegate),
341
+ [usage description key](https://developer.apple.com/documentation/bundleresources/information-property-list/nsbluetoothalwaysusagedescription),
342
+ [authorization values](https://developer.apple.com/documentation/corebluetooth/cbmanagerauthorization),
343
+ [scanning](https://developer.apple.com/documentation/corebluetooth/cbcentralmanager/scanforperipherals(withservices:options:)),
344
+ [advertising](https://developer.apple.com/documentation/corebluetooth/cbperipheralmanager/startadvertising(_:)),
345
+ [writing values](https://developer.apple.com/documentation/corebluetooth/cbperipheral) (`writeValue(_:for:type:)` on the peripheral page),
346
+ [maximum write length](https://developer.apple.com/documentation/corebluetooth/cbperipheral/maximumwritevaluelength(for:)),
347
+ [write-without-response readiness](https://developer.apple.com/documentation/corebluetooth/cbperipheral/cansendwritewithoutresponse),
348
+ [background execution modes](https://developer.apple.com/documentation/xcode/configuring-background-execution-modes)