@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.
- package/CHANGELOG.md +27 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +328 -329
- package/package.json +2 -2
- package/pipeline/multi-agent-refs/features/design-conformance.md +62 -64
- package/pipeline/scripts/_notices.mjs +12 -1
- package/pipeline/scripts/gen-skills-index.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +106 -106
- package/pipeline/skills/shared/README.md +71 -71
- package/pipeline/skills/shared/external/agent-introspection-debugging/SKILL.md +1 -0
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
- package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
- package/pipeline/skills/shared/external/android-architecture/SKILL.md +2 -0
- package/pipeline/skills/shared/external/android-performance/SKILL.md +2 -0
- package/pipeline/skills/shared/external/android-security/SKILL.md +2 -0
- package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
- package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
- package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
- package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
- package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
- package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
- package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
- package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
- package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
- package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
- package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
- package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
- package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
- package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
- package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
- package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
- package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
- package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
- package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
- package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
- package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
- package/pipeline/skills/shared/external/backlog/BACKLOG.md +1 -1
- package/pipeline/skills/shared/external/backlog/SKILL.md +56 -33
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
- package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
- package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +1 -0
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
- package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
- package/pipeline/skills/shared/external/compose-components/SKILL.md +2 -0
- package/pipeline/skills/shared/external/compose-navigation/SKILL.md +3 -2
- package/pipeline/skills/shared/external/compose-testing/SKILL.md +2 -0
- package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
- package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
- package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
- package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
- package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
- package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
- package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
- package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
- package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
- package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
- package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
- package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
- package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
- package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
- package/pipeline/skills/shared/external/council/SKILL.md +1 -0
- package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
- package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
- package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
- package/pipeline/skills/shared/external/css-modern/SKILL.md +1 -0
- package/pipeline/skills/shared/external/database-patterns/SKILL.md +1 -0
- package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
- package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
- package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
- package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
- package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
- package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
- package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
- package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
- package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
- package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
- package/pipeline/skills/shared/external/evidence-github/SKILL.md +2 -0
- package/pipeline/skills/shared/external/evidence-registry/SKILL.md +2 -0
- package/pipeline/skills/shared/external/gradle-kotlin-dsl/SKILL.md +2 -0
- package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
- package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
- package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
- package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
- package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
- package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
- package/pipeline/skills/shared/external/html-semantic/SKILL.md +1 -0
- package/pipeline/skills/shared/external/humanizer/SKILL.md +1 -0
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
- package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
- package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
- package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
- package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
- package/pipeline/skills/shared/external/ios-coding-standard/SKILL.md +1 -0
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
- package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
- package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
- package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
- package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +1 -0
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
- package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
- package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
- package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
- package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
- package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
- package/pipeline/skills/shared/external/ios-security/SKILL.md +2 -0
- package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
- package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
- package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
- package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
- package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +91 -283
- package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +119 -151
- package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +60 -90
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +119 -156
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +726 -787
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +253 -288
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +243 -304
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +88 -104
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +181 -235
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +198 -263
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +461 -466
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +145 -151
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +123 -141
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +146 -157
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/snapshot-resources.sh +22 -19
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +156 -140
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
- package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
- package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
- package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
- package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
- package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
- package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
- package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
- package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
- package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
- package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +1 -0
- package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
- package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
- package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
- package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
- package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
- package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
- package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
- package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
- package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
- package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
- package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
- package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
- package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
- package/pipeline/skills/shared/external/play-store-review/SKILL.md +2 -0
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
- package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
- package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
- package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
- package/pipeline/skills/shared/external/python-patterns/SKILL.md +1 -0
- package/pipeline/skills/shared/external/react-best-practices/SKILL.md +1 -0
- package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
- package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
- package/pipeline/skills/shared/external/rest-api-design/SKILL.md +1 -0
- package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +2 -0
- package/pipeline/skills/shared/external/room-database/SKILL.md +2 -0
- package/pipeline/skills/shared/external/search-first/SKILL.md +1 -0
- package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
- package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
- package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
- package/pipeline/skills/shared/external/signal-community/SKILL.md +2 -0
- package/pipeline/skills/shared/external/skill-creator/SKILL.md +80 -41
- package/pipeline/skills/shared/external/skill-creator/audit.md +63 -59
- package/pipeline/skills/shared/external/skill-creator/checklist.md +28 -20
- package/pipeline/skills/shared/external/skill-creator/examples.md +40 -40
- package/pipeline/skills/shared/external/skill-creator/label-check.md +48 -36
- package/pipeline/skills/shared/external/skill-creator/scripts/audit-panel.js +91 -100
- package/pipeline/skills/shared/external/skill-creator/template.md +51 -39
- package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
- package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
- package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
- package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
- package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
- package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
- package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
- package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
- package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
- package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
- package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
- package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
- package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
- package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
- package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
- package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
- package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
- package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
- package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
- package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
- package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
- package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
- package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
- package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
- package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
- package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
- package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
- package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
- package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
- package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
- package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
- package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
- package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
- package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
- package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
- package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
- package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
- package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
- package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
- package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
- package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
- package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
- package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
- package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
- package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
- package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
- package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
- package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
- package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
- package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
- package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
- package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
- package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
- package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
- package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
- package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
- package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
- package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
- package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
- package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
- package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
- package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
- package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
- package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
- package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
- package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
- package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
- package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
- package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
- package/pipeline/skills/shared/external/tailwind-css/SKILL.md +1 -0
- package/pipeline/skills/shared/external/testing-backend/SKILL.md +1 -0
- package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
- package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
- package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +1 -0
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
- package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
- package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
- package/pipeline/skills/shared/external/vue-composition/SKILL.md +1 -0
- package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- package/pipeline/skills/shared/external/web-accessibility/SKILL.md +1 -0
- package/pipeline/skills/shared/external/web-performance/SKILL.md +1 -0
- package/pipeline/skills/shared/external/web-testing/SKILL.md +1 -0
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md
CHANGED
|
@@ -1,583 +1,443 @@
|
|
|
1
|
-
# SharePlay
|
|
1
|
+
# SharePlay Patterns
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Longer examples for the `shareplay-activities` skill. SKILL.md covers setup,
|
|
4
|
+
the session lifecycle and the messenger basics; this file builds on them.
|
|
5
5
|
|
|
6
6
|
## Contents
|
|
7
7
|
|
|
8
|
-
- [
|
|
9
|
-
- [
|
|
10
|
-
- [SwiftUI
|
|
11
|
-
|
|
12
|
-
- [
|
|
13
|
-
- [
|
|
8
|
+
- [A shared sticky-note board](#a-shared-sticky-note-board)
|
|
9
|
+
- [A reusable manager](#a-reusable-manager)
|
|
10
|
+
- [SwiftUI entry points](#swiftui-entry-points)
|
|
11
|
+
- [Syncing game state, including late joiners](#syncing-game-state-including-late-joiners)
|
|
12
|
+
- [Tracking who joins and leaves](#tracking-who-joins-and-leaves)
|
|
13
|
+
- [Nearby participants on visionOS](#nearby-participants-on-visionos)
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## A shared sticky-note board
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Several people drag notes around one board. Positions change constantly, while
|
|
18
|
+
clearing the board must never be lost.
|
|
18
19
|
|
|
19
20
|
```swift
|
|
20
21
|
import GroupActivities
|
|
22
|
+
import Observation
|
|
23
|
+
import SwiftUI
|
|
21
24
|
|
|
22
|
-
struct
|
|
23
|
-
static let activityIdentifier = "
|
|
25
|
+
struct NoteBoard: GroupActivity, Sendable {
|
|
26
|
+
static let activityIdentifier = "org.sample.board.notes"
|
|
24
27
|
|
|
25
28
|
var metadata: GroupActivityMetadata {
|
|
26
|
-
var
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return
|
|
29
|
+
var info = GroupActivityMetadata()
|
|
30
|
+
info.title = "Plan on one board"
|
|
31
|
+
info.type = .createTogether
|
|
32
|
+
return info
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
|
-
```
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
```swift
|
|
37
|
-
import Foundation
|
|
36
|
+
struct Spot: Codable, Sendable, Hashable { var x: Double; var y: Double }
|
|
37
|
+
struct Tint: Codable, Sendable, Hashable { var red: Double; var green: Double; var blue: Double }
|
|
38
38
|
|
|
39
|
-
struct
|
|
39
|
+
struct NoteMoved: Codable, Sendable, Identifiable {
|
|
40
40
|
let id: UUID
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
struct CGPointCodable: Codable, Sendable {
|
|
46
|
-
let x: Double
|
|
47
|
-
let y: Double
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
struct ColorCodable: Codable, Sendable {
|
|
51
|
-
let red: Double
|
|
52
|
-
let green: Double
|
|
53
|
-
let blue: Double
|
|
54
|
-
let alpha: Double
|
|
55
|
-
}
|
|
41
|
+
var trail: [Spot]
|
|
42
|
+
var tint: Tint
|
|
43
|
+
var size: Double
|
|
56
44
|
}
|
|
57
45
|
|
|
58
|
-
struct
|
|
59
|
-
let timestamp: Date
|
|
60
|
-
}
|
|
46
|
+
struct BoardCleared: Codable, Sendable {}
|
|
61
47
|
```
|
|
62
48
|
|
|
63
|
-
|
|
49
|
+
`CGPoint` and `Color` are not `Codable`, so the message uses small codable
|
|
50
|
+
stand-ins for points and colours.
|
|
64
51
|
|
|
65
52
|
```swift
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
private var
|
|
72
|
-
private var
|
|
73
|
-
private var
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
) {
|
|
90
|
-
// Clean up previous session
|
|
91
|
-
cleanUp()
|
|
92
|
-
|
|
93
|
-
self.session = session
|
|
94
|
-
self.reliableMessenger = GroupSessionMessenger(
|
|
95
|
-
session: session,
|
|
96
|
-
deliveryMode: .reliable
|
|
97
|
-
)
|
|
98
|
-
self.unreliableMessenger = GroupSessionMessenger(
|
|
99
|
-
session: session,
|
|
100
|
-
deliveryMode: .unreliable
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
// Observe state
|
|
104
|
-
let stateTask = Task {
|
|
105
|
-
for await state in session.$state.values {
|
|
106
|
-
switch state {
|
|
107
|
-
case .joined:
|
|
108
|
-
isConnected = true
|
|
109
|
-
case .invalidated:
|
|
110
|
-
isConnected = false
|
|
111
|
-
cleanUp()
|
|
112
|
-
default:
|
|
113
|
-
break
|
|
53
|
+
@Observable @MainActor
|
|
54
|
+
final class BoardSync {
|
|
55
|
+
private(set) var notes: [UUID: NoteMoved] = [:]
|
|
56
|
+
private(set) var connected = false
|
|
57
|
+
private var session: GroupSession<NoteBoard>?
|
|
58
|
+
private var fast: GroupSessionMessenger?
|
|
59
|
+
private var safe: GroupSessionMessenger?
|
|
60
|
+
private var jobs: [Task<Void, Never>] = []
|
|
61
|
+
|
|
62
|
+
func configure(_ incoming: GroupSession<NoteBoard>) {
|
|
63
|
+
cleanup()
|
|
64
|
+
session = incoming
|
|
65
|
+
let quick = GroupSessionMessenger(session: incoming, deliveryMode: .unreliable)
|
|
66
|
+
let sure = GroupSessionMessenger(session: incoming, deliveryMode: .reliable)
|
|
67
|
+
fast = quick
|
|
68
|
+
safe = sure
|
|
69
|
+
|
|
70
|
+
jobs.append(Task { @MainActor [weak self] in
|
|
71
|
+
for await state in incoming.$state.values {
|
|
72
|
+
if case .invalidated = state {
|
|
73
|
+
self?.cleanup()
|
|
74
|
+
} else if case .joined = state {
|
|
75
|
+
self?.connected = true
|
|
114
76
|
}
|
|
115
77
|
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
let strokeTask = Task {
|
|
121
|
-
guard let messenger = unreliableMessenger else { return }
|
|
122
|
-
for await (stroke, _) in messenger.messages(of: StrokeMessage.self) {
|
|
123
|
-
strokes.append(stroke)
|
|
78
|
+
})
|
|
79
|
+
jobs.append(Task { @MainActor [weak self] in
|
|
80
|
+
for await (note, _) in quick.messages(of: NoteMoved.self) {
|
|
81
|
+
self?.notes[note.id] = note
|
|
124
82
|
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
let clearTask = Task {
|
|
130
|
-
guard let messenger = reliableMessenger else { return }
|
|
131
|
-
for await (_, _) in messenger.messages(of: ClearCanvasMessage.self) {
|
|
132
|
-
strokes.removeAll()
|
|
83
|
+
})
|
|
84
|
+
jobs.append(Task { @MainActor [weak self] in
|
|
85
|
+
for await _ in sure.messages(of: BoardCleared.self) {
|
|
86
|
+
self?.notes.removeAll()
|
|
133
87
|
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
session.join()
|
|
88
|
+
})
|
|
89
|
+
incoming.join()
|
|
138
90
|
}
|
|
139
91
|
|
|
140
|
-
func
|
|
141
|
-
|
|
142
|
-
|
|
92
|
+
func move(_ note: NoteMoved) {
|
|
93
|
+
notes[note.id] = note
|
|
94
|
+
guard let fast else { return }
|
|
95
|
+
Task { try? await fast.send(note, to: .all) }
|
|
143
96
|
}
|
|
144
97
|
|
|
145
|
-
func
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
to: .all
|
|
150
|
-
)
|
|
98
|
+
func clearBoard() {
|
|
99
|
+
notes.removeAll()
|
|
100
|
+
guard let safe else { return }
|
|
101
|
+
Task { try? await safe.send(BoardCleared(), to: .all) }
|
|
151
102
|
}
|
|
152
103
|
|
|
153
104
|
func leave() {
|
|
105
|
+
defer { cleanup() }
|
|
154
106
|
session?.leave()
|
|
155
|
-
cleanUp()
|
|
156
107
|
}
|
|
157
108
|
|
|
158
|
-
private func
|
|
159
|
-
|
|
160
|
-
|
|
109
|
+
private func cleanup() {
|
|
110
|
+
for job in jobs { job.cancel() }
|
|
111
|
+
jobs = []
|
|
112
|
+
fast = nil
|
|
113
|
+
safe = nil
|
|
161
114
|
session = nil
|
|
162
|
-
|
|
163
|
-
unreliableMessenger = nil
|
|
164
|
-
isConnected = false
|
|
115
|
+
connected = false
|
|
165
116
|
}
|
|
166
117
|
}
|
|
167
118
|
```
|
|
168
119
|
|
|
169
|
-
|
|
120
|
+
Two messengers share one session: note moves go over the unreliable one for
|
|
121
|
+
speed, and clearing goes over the reliable one so nobody misses it. Local
|
|
122
|
+
changes apply on this device first and are then sent to everyone.
|
|
123
|
+
`configure` starts by cleaning up any earlier session.
|
|
170
124
|
|
|
171
|
-
|
|
125
|
+
## A reusable manager
|
|
126
|
+
|
|
127
|
+
A generic manager that any activity can use.
|
|
172
128
|
|
|
173
129
|
```swift
|
|
174
130
|
import GroupActivities
|
|
131
|
+
import Observation
|
|
175
132
|
|
|
176
|
-
|
|
177
|
-
|
|
133
|
+
enum SharePlayError: Error { case notConnected }
|
|
134
|
+
|
|
135
|
+
@MainActor @Observable
|
|
178
136
|
final class SharePlayManager<Activity: GroupActivity> {
|
|
137
|
+
private(set) var participants: Set<Participant> = []
|
|
138
|
+
private(set) var journal: GroupSessionJournal?
|
|
179
139
|
private(set) var session: GroupSession<Activity>?
|
|
180
140
|
private(set) var messenger: GroupSessionMessenger?
|
|
181
|
-
private
|
|
141
|
+
private var jobs: [Task<Void, Never>] = []
|
|
182
142
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
// GroupSessionJournal requires iOS/iPadOS/tvOS 17+, macOS 14+,
|
|
190
|
-
// or visionOS 1+. Gate this property if your deployment target is older.
|
|
143
|
+
var localParticipant: Participant? { session?.localParticipant }
|
|
144
|
+
var isJoined: Bool {
|
|
145
|
+
if case .joined? = session?.state { return true }
|
|
146
|
+
return false
|
|
147
|
+
}
|
|
191
148
|
|
|
192
|
-
func startObserving() {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
configure(session)
|
|
196
|
-
}
|
|
149
|
+
func startObserving() async {
|
|
150
|
+
for await incoming in Activity.sessions() {
|
|
151
|
+
adopt(incoming)
|
|
197
152
|
}
|
|
198
153
|
}
|
|
199
154
|
|
|
200
|
-
private func
|
|
155
|
+
private func adopt(_ incoming: GroupSession<Activity>) {
|
|
201
156
|
reset()
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
let stateTask = Task {
|
|
209
|
-
for await state in session.$state.values {
|
|
210
|
-
switch state {
|
|
211
|
-
case .joined:
|
|
212
|
-
isJoined = true
|
|
213
|
-
case .invalidated:
|
|
214
|
-
isJoined = false
|
|
215
|
-
reset()
|
|
216
|
-
default:
|
|
217
|
-
break
|
|
218
|
-
}
|
|
157
|
+
session = incoming
|
|
158
|
+
messenger = GroupSessionMessenger(session: incoming)
|
|
159
|
+
journal = GroupSessionJournal(session: incoming)
|
|
160
|
+
jobs.append(Task { @MainActor [weak self] in
|
|
161
|
+
for await current in incoming.$activeParticipants.values {
|
|
162
|
+
self?.participants = current
|
|
219
163
|
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
for await participants in session.$activeParticipants.values {
|
|
225
|
-
activeParticipants = participants
|
|
164
|
+
})
|
|
165
|
+
jobs.append(Task { @MainActor [weak self] in
|
|
166
|
+
for await state in incoming.$state.values {
|
|
167
|
+
if case .invalidated = state { self?.reset() }
|
|
226
168
|
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
session.join()
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
func leave() {
|
|
234
|
-
session?.leave()
|
|
235
|
-
reset()
|
|
169
|
+
})
|
|
170
|
+
incoming.join()
|
|
236
171
|
}
|
|
237
172
|
|
|
238
173
|
func end() {
|
|
174
|
+
defer { reset() }
|
|
239
175
|
session?.end()
|
|
240
|
-
reset()
|
|
241
176
|
}
|
|
242
177
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
session = nil
|
|
247
|
-
messenger = nil
|
|
248
|
-
journal = nil
|
|
249
|
-
isJoined = false
|
|
250
|
-
activeParticipants = []
|
|
178
|
+
func leave() {
|
|
179
|
+
defer { reset() }
|
|
180
|
+
session?.leave()
|
|
251
181
|
}
|
|
252
|
-
}
|
|
253
|
-
```
|
|
254
182
|
|
|
255
|
-
|
|
183
|
+
private func connectedMessenger() throws -> GroupSessionMessenger {
|
|
184
|
+
guard let messenger else { throw SharePlayError.notConnected }
|
|
185
|
+
return messenger
|
|
186
|
+
}
|
|
256
187
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
func send<T: Codable>(_ message: T) async throws {
|
|
260
|
-
guard let messenger else {
|
|
261
|
-
throw SharePlayError.notConnected
|
|
262
|
-
}
|
|
263
|
-
try await messenger.send(message, to: .all)
|
|
188
|
+
func broadcast<Payload: Codable & Sendable>(_ payload: Payload) async throws {
|
|
189
|
+
try await connectedMessenger().send(payload, to: .all)
|
|
264
190
|
}
|
|
265
191
|
|
|
266
|
-
func send<
|
|
267
|
-
|
|
268
|
-
throw SharePlayError.notConnected
|
|
269
|
-
}
|
|
270
|
-
try await messenger.send(message, to: .only(participant))
|
|
192
|
+
func send<Payload: Codable & Sendable>(_ payload: Payload, to person: Participant) async throws {
|
|
193
|
+
try await connectedMessenger().send(payload, to: .only(person))
|
|
271
194
|
}
|
|
272
195
|
|
|
273
|
-
func messages<
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
196
|
+
func messages<Payload: Codable & Sendable>(of kind: Payload.Type) -> AsyncThrowingStream<(Payload, Participant), Error> {
|
|
197
|
+
let source = messenger
|
|
198
|
+
return AsyncThrowingStream { sink in
|
|
199
|
+
guard let source else {
|
|
200
|
+
sink.finish(throwing: SharePlayError.notConnected)
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
let pump = Task {
|
|
204
|
+
for await (payload, ctx) in source.messages(of: kind) {
|
|
205
|
+
sink.yield((payload, ctx.source))
|
|
282
206
|
}
|
|
283
|
-
|
|
207
|
+
sink.finish()
|
|
284
208
|
}
|
|
285
|
-
|
|
209
|
+
sink.onTermination = { _ in pump.cancel() }
|
|
286
210
|
}
|
|
287
211
|
}
|
|
288
|
-
}
|
|
289
212
|
|
|
290
|
-
|
|
291
|
-
|
|
213
|
+
private func reset() {
|
|
214
|
+
for job in jobs { job.cancel() }
|
|
215
|
+
jobs = []
|
|
216
|
+
participants = []
|
|
217
|
+
journal = nil
|
|
218
|
+
messenger = nil
|
|
219
|
+
session = nil
|
|
220
|
+
}
|
|
292
221
|
}
|
|
293
222
|
```
|
|
294
223
|
|
|
295
|
-
|
|
224
|
+
The `journal` property requires the 17 releases of iOS, iPadOS and tvOS, macOS
|
|
225
|
+
14, or the first visionOS.
|
|
226
|
+
With an older deployment target, gate it with `@available` or `#available`.
|
|
227
|
+
`leave()` leaves and resets; `end()` ends the session for everyone and resets.
|
|
228
|
+
The typed send helpers throw `notConnected` when no messenger exists yet.
|
|
229
|
+
|
|
230
|
+
## SwiftUI entry points
|
|
296
231
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
`GroupActivitySharingController` when the user needs to choose participants.
|
|
232
|
+
A custom SharePlay button only makes sense while the person is already on a
|
|
233
|
+
FaceTime call or in a Messages chat. When the person still has to choose who to
|
|
234
|
+
share with, use `ShareLink`, a share sheet or `GroupActivitySharingController`.
|
|
301
235
|
|
|
302
236
|
### ShareLink and AirDrop
|
|
303
237
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
238
|
+
`ShareLink`, SharePlay over AirDrop and the system share sheets all need the
|
|
239
|
+
activity to be `Transferable`. Keep what it carries small, such as an ID or a
|
|
240
|
+
URL, and load the heavy content after joining. `Transferable` requires
|
|
241
|
+
`Sendable`, and a struct only gets that implicitly inside its own file, which is
|
|
242
|
+
why `NoteBoard` above declares `Sendable` explicitly.
|
|
307
243
|
|
|
308
244
|
```swift
|
|
309
|
-
import CoreTransferable
|
|
310
|
-
import GroupActivities
|
|
311
245
|
import SwiftUI
|
|
246
|
+
import GroupActivities
|
|
247
|
+
import CoreTransferable
|
|
312
248
|
|
|
313
|
-
extension
|
|
249
|
+
extension NoteBoard: Transferable {
|
|
314
250
|
static var transferRepresentation: some TransferRepresentation {
|
|
315
|
-
GroupActivityTransferRepresentation {
|
|
316
|
-
activity
|
|
317
|
-
}
|
|
251
|
+
GroupActivityTransferRepresentation { $0 }
|
|
318
252
|
}
|
|
319
253
|
}
|
|
320
254
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
255
|
+
struct InviteButton: View {
|
|
256
|
+
var body: some View {
|
|
257
|
+
ShareLink(item: NoteBoard(), preview: SharePreview("Plan on one board"))
|
|
258
|
+
}
|
|
259
|
+
}
|
|
325
260
|
```
|
|
326
261
|
|
|
327
|
-
### SharePlay
|
|
262
|
+
### A SharePlay button
|
|
328
263
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
264
|
+
`GroupActivity` does not require `Sendable`, so the generic parameter adds it:
|
|
265
|
+
the button's `Task` hands the activity to the nonisolated
|
|
266
|
+
`prepareForActivation()` and `activate()`.
|
|
332
267
|
|
|
333
|
-
|
|
268
|
+
```swift
|
|
269
|
+
struct SharePlayButton<Activity: GroupActivity & Sendable>: View {
|
|
270
|
+
@State private var groupState = GroupStateObserver()
|
|
334
271
|
let activity: Activity
|
|
335
272
|
|
|
336
|
-
@State private var observer = GroupStateObserver()
|
|
337
|
-
|
|
338
273
|
var body: some View {
|
|
339
|
-
if
|
|
274
|
+
if groupState.isEligibleForGroupSession {
|
|
340
275
|
Button {
|
|
341
276
|
Task {
|
|
342
|
-
|
|
277
|
+
guard case .activationPreferred = await activity.prepareForActivation() else { return }
|
|
278
|
+
_ = try? await activity.activate()
|
|
343
279
|
}
|
|
344
280
|
} label: {
|
|
345
281
|
Label("SharePlay", systemImage: "shareplay")
|
|
346
282
|
}
|
|
347
283
|
}
|
|
348
284
|
}
|
|
349
|
-
|
|
350
|
-
private func startActivity() async throws {
|
|
351
|
-
switch await activity.prepareForActivation() {
|
|
352
|
-
case .activationPreferred:
|
|
353
|
-
_ = try await activity.activate()
|
|
354
|
-
case .activationDisabled:
|
|
355
|
-
break
|
|
356
|
-
case .cancelled:
|
|
357
|
-
break
|
|
358
|
-
@unknown default:
|
|
359
|
-
break
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
285
|
}
|
|
363
286
|
```
|
|
364
287
|
|
|
365
|
-
###
|
|
288
|
+
### A status badge
|
|
366
289
|
|
|
367
290
|
```swift
|
|
368
|
-
struct
|
|
369
|
-
let
|
|
370
|
-
let
|
|
291
|
+
struct SharePlayBadge: View {
|
|
292
|
+
let connected: Bool
|
|
293
|
+
let count: Int
|
|
371
294
|
|
|
372
295
|
var body: some View {
|
|
373
|
-
if
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
.font(.caption)
|
|
379
|
-
.foregroundStyle(.secondary)
|
|
380
|
-
}
|
|
296
|
+
if connected {
|
|
297
|
+
Label("\(count)", systemImage: "shareplay")
|
|
298
|
+
.font(.caption)
|
|
299
|
+
.padding(6)
|
|
300
|
+
.background(.thinMaterial, in: Capsule())
|
|
381
301
|
}
|
|
382
302
|
}
|
|
383
303
|
}
|
|
384
304
|
```
|
|
385
305
|
|
|
386
|
-
###
|
|
306
|
+
### Putting it in a screen
|
|
387
307
|
|
|
388
|
-
Create the
|
|
389
|
-
|
|
390
|
-
|
|
308
|
+
Create the manager at the app, scene or feature-coordinator level and inject
|
|
309
|
+
it. The screen reads it from the environment and never owns the `sessions()`
|
|
310
|
+
lifecycle itself.
|
|
391
311
|
|
|
392
312
|
```swift
|
|
393
|
-
struct
|
|
394
|
-
@Environment(SharePlayManager<
|
|
395
|
-
|
|
396
|
-
let movieID: String
|
|
397
|
-
let movieTitle: String
|
|
313
|
+
struct BoardScreen: View {
|
|
314
|
+
@Environment(SharePlayManager<NoteBoard>.self) private var shared
|
|
398
315
|
|
|
399
316
|
var body: some View {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
HStack {
|
|
404
|
-
SharePlayButton(
|
|
405
|
-
activity: WatchTogetherActivity(
|
|
406
|
-
movieID: movieID,
|
|
407
|
-
movieTitle: movieTitle
|
|
408
|
-
)
|
|
409
|
-
)
|
|
410
|
-
|
|
411
|
-
if manager.isJoined {
|
|
412
|
-
SharePlayStatusView(
|
|
413
|
-
participantCount: manager.activeParticipants.count,
|
|
414
|
-
isConnected: true
|
|
415
|
-
)
|
|
416
|
-
}
|
|
317
|
+
BoardCanvas()
|
|
318
|
+
.overlay(alignment: .topTrailing) {
|
|
319
|
+
SharePlayBadge(connected: shared.isJoined, count: shared.participants.count)
|
|
417
320
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
321
|
+
.toolbar { SharePlayButton(activity: NoteBoard()) }
|
|
322
|
+
.task { await shared.startObserving() }
|
|
323
|
+
.onDisappear { shared.leave() }
|
|
421
324
|
}
|
|
422
325
|
}
|
|
423
326
|
```
|
|
424
327
|
|
|
425
|
-
##
|
|
328
|
+
## Syncing game state, including late joiners
|
|
426
329
|
|
|
427
|
-
|
|
330
|
+
A shared word puzzle. Whoever started the session hosts it and brings each
|
|
331
|
+
newcomer up to date.
|
|
428
332
|
|
|
429
333
|
```swift
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
struct QuizActivity: GroupActivity {
|
|
433
|
-
let quizID: String
|
|
434
|
-
|
|
334
|
+
struct WordRace: GroupActivity {
|
|
335
|
+
let puzzleID: UUID
|
|
435
336
|
var metadata: GroupActivityMetadata {
|
|
436
|
-
var
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
return
|
|
337
|
+
var info = GroupActivityMetadata()
|
|
338
|
+
info.title = "Word race"
|
|
339
|
+
info.type = .generic
|
|
340
|
+
return info
|
|
440
341
|
}
|
|
441
342
|
}
|
|
442
343
|
|
|
443
|
-
|
|
444
|
-
struct
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
```swift
|
|
464
|
-
@Observable
|
|
465
|
-
@MainActor
|
|
466
|
-
final class QuizManager {
|
|
467
|
-
private var session: GroupSession<QuizActivity>?
|
|
468
|
-
private var messenger: GroupSessionMessenger?
|
|
469
|
-
private var tasks: [Task<Void, Never>] = []
|
|
470
|
-
|
|
471
|
-
var currentQuestion: QuizQuestion?
|
|
472
|
-
var scores: [String: Int] = [:]
|
|
473
|
-
var isHost = false
|
|
474
|
-
|
|
475
|
-
func configureSession(_ session: GroupSession<QuizActivity>) {
|
|
476
|
-
self.session = session
|
|
477
|
-
self.messenger = GroupSessionMessenger(session: session)
|
|
478
|
-
self.isHost = session.isLocallyInitiated
|
|
479
|
-
|
|
480
|
-
let questionTask = Task {
|
|
481
|
-
guard let messenger else { return }
|
|
482
|
-
for await (question, _) in messenger.messages(of: QuizQuestion.self) {
|
|
483
|
-
currentQuestion = question
|
|
344
|
+
struct NewClue: Codable, Sendable { let number: Int; let clue: String }
|
|
345
|
+
struct Guess: Codable, Sendable { let number: Int; let word: String }
|
|
346
|
+
struct RaceSnapshot: Codable, Sendable { let clue: NewClue?; let points: [String: Int] }
|
|
347
|
+
|
|
348
|
+
@Observable @MainActor
|
|
349
|
+
final class WordRaceHost {
|
|
350
|
+
private(set) var clue: NewClue?
|
|
351
|
+
private(set) var points: [String: Int] = [:]
|
|
352
|
+
private var jobs: [Task<Void, Never>] = []
|
|
353
|
+
|
|
354
|
+
func run(_ session: GroupSession<WordRace>) {
|
|
355
|
+
let messenger = GroupSessionMessenger(session: session)
|
|
356
|
+
let hosting = session.isLocallyInitiated
|
|
357
|
+
|
|
358
|
+
jobs.append(Task { @MainActor [weak self] in
|
|
359
|
+
for await (next, _) in messenger.messages(of: NewClue.self) { self?.clue = next }
|
|
360
|
+
})
|
|
361
|
+
jobs.append(Task { @MainActor [weak self] in
|
|
362
|
+
for await (guess, context) in messenger.messages(of: Guess.self) {
|
|
363
|
+
self?.score(guess, from: context.source)
|
|
484
364
|
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
for await (answer, context) in messenger.messages(of: QuizAnswer.self) {
|
|
491
|
-
processAnswer(answer, from: context.source)
|
|
365
|
+
})
|
|
366
|
+
jobs.append(Task { @MainActor [weak self] in
|
|
367
|
+
for await (snapshot, _) in messenger.messages(of: RaceSnapshot.self) {
|
|
368
|
+
self?.clue = snapshot.clue
|
|
369
|
+
self?.points = snapshot.points
|
|
492
370
|
}
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
try? await messenger?.send(question, to: .only(joiner))
|
|
371
|
+
})
|
|
372
|
+
jobs.append(Task { @MainActor [weak self] in
|
|
373
|
+
var seen = Set<Participant>()
|
|
374
|
+
for await present in session.$activeParticipants.values {
|
|
375
|
+
let arrivals = present.subtracting(seen)
|
|
376
|
+
seen = present
|
|
377
|
+
guard hosting, let self else { continue }
|
|
378
|
+
for newcomer in arrivals {
|
|
379
|
+
if let clue = self.clue {
|
|
380
|
+
try? await messenger.send(clue, to: .only(newcomer))
|
|
504
381
|
}
|
|
505
|
-
let
|
|
506
|
-
|
|
507
|
-
scores: scores
|
|
508
|
-
)
|
|
509
|
-
try? await messenger?.send(state, to: .only(joiner))
|
|
382
|
+
let snapshot = RaceSnapshot(clue: self.clue, points: self.points)
|
|
383
|
+
try? await messenger.send(snapshot, to: .only(newcomer))
|
|
510
384
|
}
|
|
511
|
-
known = participants
|
|
512
385
|
}
|
|
513
|
-
}
|
|
514
|
-
tasks.append(participantTask)
|
|
515
|
-
|
|
386
|
+
})
|
|
516
387
|
session.join()
|
|
517
388
|
}
|
|
518
389
|
|
|
519
|
-
func
|
|
520
|
-
guard
|
|
521
|
-
|
|
522
|
-
questionID: question.questionID,
|
|
523
|
-
selectedOption: option
|
|
524
|
-
)
|
|
525
|
-
try? await messenger?.send(answer, to: .all)
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
private func processAnswer(_ answer: QuizAnswer, from participant: Participant) {
|
|
529
|
-
// Score the answer and update scores
|
|
530
|
-
let key = participant.id.uuidString
|
|
531
|
-
scores[key, default: 0] += 1
|
|
390
|
+
private func score(_ guess: Guess, from player: Participant) {
|
|
391
|
+
guard guess.number == clue?.number else { return }
|
|
392
|
+
points[player.id.uuidString, default: 0] += 1
|
|
532
393
|
}
|
|
533
394
|
}
|
|
534
395
|
```
|
|
535
396
|
|
|
536
|
-
|
|
397
|
+
- `session.isLocallyInitiated` marks the device that started the session, which
|
|
398
|
+
acts as host.
|
|
399
|
+
- Each message type gets its own task over `messages(of:)`; the guess handler
|
|
400
|
+
reads the sender from `context.source`.
|
|
401
|
+
- The participant task remembers who it has seen, and for every new arrival
|
|
402
|
+
the host sends the current clue and then a full snapshot `to: .only(...)`.
|
|
403
|
+
- Points are keyed by `participant.id.uuidString`.
|
|
537
404
|
|
|
538
|
-
|
|
405
|
+
## Tracking who joins and leaves
|
|
539
406
|
|
|
540
407
|
```swift
|
|
541
|
-
@Observable
|
|
542
408
|
@MainActor
|
|
543
|
-
final class
|
|
544
|
-
private var
|
|
545
|
-
|
|
546
|
-
func handleParticipantUpdate(
|
|
547
|
-
_ activeParticipants: Set<Participant>,
|
|
548
|
-
sendStateTo: (Participant) async throws -> Void
|
|
549
|
-
) async {
|
|
550
|
-
let joined = activeParticipants.subtracting(knownParticipants)
|
|
551
|
-
let left = knownParticipants.subtracting(activeParticipants)
|
|
552
|
-
|
|
553
|
-
for participant in joined {
|
|
554
|
-
print("Participant joined: \(participant.id)")
|
|
555
|
-
try? await sendStateTo(participant)
|
|
556
|
-
}
|
|
409
|
+
final class RosterTracker {
|
|
410
|
+
private var known: Set<Participant> = []
|
|
411
|
+
private let welcome: (Participant) async -> Void
|
|
557
412
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
413
|
+
init(welcome: @escaping (Participant) async -> Void) {
|
|
414
|
+
self.welcome = welcome
|
|
415
|
+
}
|
|
561
416
|
|
|
562
|
-
|
|
417
|
+
func update(with active: Set<Participant>) async {
|
|
418
|
+
let joined = active.subtracting(known)
|
|
419
|
+
let departed = known.subtracting(active)
|
|
420
|
+
for person in joined { await welcome(person) }
|
|
421
|
+
if !departed.isEmpty { Log.share.info("\(departed.count) left") }
|
|
422
|
+
known = active
|
|
563
423
|
}
|
|
564
424
|
}
|
|
565
425
|
```
|
|
566
426
|
|
|
567
|
-
|
|
427
|
+
## Nearby participants on visionOS
|
|
568
428
|
|
|
569
|
-
On visionOS 26
|
|
570
|
-
|
|
571
|
-
FaceTime participants
|
|
572
|
-
|
|
429
|
+
On visionOS 26 and later, people wearing Vision Pro headsets in the same room can
|
|
430
|
+
join the same activity. Sessions, messengers and journals treat them exactly
|
|
431
|
+
like FaceTime participants. `participant.isNearbyWithLocalParticipant` tells you
|
|
432
|
+
who is physically present; check it only when the interface or spatial
|
|
433
|
+
placement really depends on it.
|
|
573
434
|
|
|
574
435
|
```swift
|
|
575
|
-
|
|
576
|
-
if
|
|
577
|
-
|
|
436
|
+
func seat(_ person: Participant, in layout: inout RoomLayout) {
|
|
437
|
+
if person.isNearbyWithLocalParticipant {
|
|
438
|
+
layout.placeBesideLocalUser(person.id)
|
|
439
|
+
} else {
|
|
440
|
+
layout.placeAsSpatialPersona(person.id)
|
|
578
441
|
}
|
|
579
442
|
}
|
|
580
443
|
```
|
|
581
|
-
|
|
582
|
-
This is useful for visionOS spatial activities where you want to offer
|
|
583
|
-
different experiences for co-located vs. remote participants.
|