@devo-bmad-custom/agent-orchestration 1.0.1 → 1.0.3
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/lib/installer.js +44 -11
- package/package.json +1 -1
- package/src/.agents/skills/audit-website/README.md +20 -20
- package/src/.agents/skills/audit-website/SKILL.md +470 -470
- package/src/.agents/skills/audit-website/agents/openai.yaml +6 -6
- package/src/.agents/skills/audit-website/assets/icon-small.svg +41 -41
- package/src/.agents/skills/audit-website/references/OUTPUT-FORMAT.md +250 -250
- package/src/.agents/skills/clean-code-standards/SKILL.md +104 -104
- package/src/.agents/skills/excalidraw-dark-standard/SKILL.md +281 -281
- package/src/.agents/skills/frontend-responsive-design-standards/SKILL.md +434 -434
- package/src/.agents/skills/java-fundamentals/SKILL.md +116 -116
- package/src/.agents/skills/java-performance/SKILL.md +119 -119
- package/src/.agents/skills/next-best-practices/SKILL.md +153 -153
- package/src/.agents/skills/next-best-practices/async-patterns.md +87 -87
- package/src/.agents/skills/next-best-practices/bundling.md +180 -180
- package/src/.agents/skills/next-best-practices/data-patterns.md +297 -297
- package/src/.agents/skills/next-best-practices/debug-tricks.md +105 -105
- package/src/.agents/skills/next-best-practices/directives.md +73 -73
- package/src/.agents/skills/next-best-practices/error-handling.md +227 -227
- package/src/.agents/skills/next-best-practices/file-conventions.md +140 -140
- package/src/.agents/skills/next-best-practices/font.md +245 -245
- package/src/.agents/skills/next-best-practices/functions.md +108 -108
- package/src/.agents/skills/next-best-practices/hydration-error.md +91 -91
- package/src/.agents/skills/next-best-practices/image.md +173 -173
- package/src/.agents/skills/next-best-practices/metadata.md +301 -301
- package/src/.agents/skills/next-best-practices/parallel-routes.md +287 -287
- package/src/.agents/skills/next-best-practices/route-handlers.md +146 -146
- package/src/.agents/skills/next-best-practices/rsc-boundaries.md +159 -159
- package/src/.agents/skills/next-best-practices/runtime-selection.md +39 -39
- package/src/.agents/skills/next-best-practices/scripts.md +141 -141
- package/src/.agents/skills/next-best-practices/self-hosting.md +371 -371
- package/src/.agents/skills/next-best-practices/suspense-boundaries.md +67 -67
- package/src/.agents/skills/nextjs-app-router-patterns/SKILL.md +537 -537
- package/src/.agents/skills/postgresql-optimization/SKILL.md +404 -404
- package/src/.agents/skills/python-backend/SKILL.md +153 -153
- package/src/.agents/skills/python-fundamentals/SKILL.md +234 -234
- package/src/.agents/skills/python-performance/SKILL.md +404 -404
- package/src/.agents/skills/react-expert/SKILL.md +335 -335
- package/src/.agents/skills/redis-best-practices/SKILL.md +438 -438
- package/src/.agents/skills/security-best-practices/SKILL.md +288 -288
- package/src/.agents/skills/security-review/LICENSE +22 -22
- package/src/.agents/skills/security-review/SKILL.md +312 -312
- package/src/.agents/skills/security-review/infrastructure/docker.md +432 -432
- package/src/.agents/skills/security-review/languages/javascript.md +388 -388
- package/src/.agents/skills/security-review/languages/python.md +363 -363
- package/src/.agents/skills/security-review/references/api-security.md +519 -519
- package/src/.agents/skills/security-review/references/authentication.md +353 -353
- package/src/.agents/skills/security-review/references/authorization.md +372 -372
- package/src/.agents/skills/security-review/references/business-logic.md +443 -443
- package/src/.agents/skills/security-review/references/cryptography.md +329 -329
- package/src/.agents/skills/security-review/references/csrf.md +398 -398
- package/src/.agents/skills/security-review/references/data-protection.md +378 -378
- package/src/.agents/skills/security-review/references/deserialization.md +410 -410
- package/src/.agents/skills/security-review/references/error-handling.md +436 -436
- package/src/.agents/skills/security-review/references/file-security.md +457 -457
- package/src/.agents/skills/security-review/references/injection.md +259 -259
- package/src/.agents/skills/security-review/references/logging.md +433 -433
- package/src/.agents/skills/security-review/references/misconfiguration.md +435 -435
- package/src/.agents/skills/security-review/references/modern-threats.md +475 -475
- package/src/.agents/skills/security-review/references/ssrf.md +415 -415
- package/src/.agents/skills/security-review/references/supply-chain.md +405 -405
- package/src/.agents/skills/security-review/references/xss.md +336 -336
- package/src/.agents/skills/subagent-driven-development/SKILL.md +275 -275
- package/src/.agents/skills/subagent-driven-development/code-quality-reviewer-prompt.md +26 -26
- package/src/.agents/skills/subagent-driven-development/implementer-prompt.md +113 -113
- package/src/.agents/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -61
- package/src/.agents/skills/systematic-debugging/CREATION-LOG.md +119 -119
- package/src/.agents/skills/systematic-debugging/SKILL.md +296 -296
- package/src/.agents/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
- package/src/.agents/skills/systematic-debugging/condition-based-waiting.md +115 -115
- package/src/.agents/skills/systematic-debugging/defense-in-depth.md +122 -122
- package/src/.agents/skills/systematic-debugging/root-cause-tracing.md +169 -169
- package/src/.agents/skills/systematic-debugging/test-academic.md +14 -14
- package/src/.agents/skills/systematic-debugging/test-pressure-1.md +58 -58
- package/src/.agents/skills/systematic-debugging/test-pressure-2.md +68 -68
- package/src/.agents/skills/systematic-debugging/test-pressure-3.md +69 -69
- package/src/.agents/skills/typescript-best-practices/SKILL.md +373 -373
- package/src/.agents/skills/ui-ux-pro-custom/SKILL.md +348 -348
- package/src/.agents/skills/ui-ux-pro-custom/data/charts.csv +26 -26
- package/src/.agents/skills/ui-ux-pro-custom/data/colors.csv +97 -97
- package/src/.agents/skills/ui-ux-pro-custom/data/icons.csv +101 -101
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/SKILL.md +106 -106
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/accessibility.md +475 -475
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/animation.md +466 -466
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/composition-locals.md +231 -231
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/deprecated-patterns.md +323 -323
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/lists-scrolling.md +400 -400
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/modifiers.md +331 -331
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/navigation.md +416 -416
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/performance.md +446 -446
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/side-effects.md +516 -516
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/source-code/foundation-source.md +13327 -13327
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/source-code/material3-source.md +19097 -19097
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/source-code/navigation-source.md +2947 -2947
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/source-code/runtime-source.md +11316 -11316
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/source-code/ui-source.md +7896 -7896
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/state-management.md +377 -377
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/styles-experimental.md +470 -470
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/theming-material3.md +349 -349
- package/src/.agents/skills/ui-ux-pro-custom/data/jetpack-compose-expert-skill/references/view-composition.md +595 -595
- package/src/.agents/skills/ui-ux-pro-custom/data/landing.csv +31 -31
- package/src/.agents/skills/ui-ux-pro-custom/data/mobile-ui-layout.md +654 -654
- package/src/.agents/skills/ui-ux-pro-custom/data/products.csv +96 -96
- package/src/.agents/skills/ui-ux-pro-custom/data/react-performance.csv +45 -45
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/astro.csv +54 -54
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/flutter.csv +53 -53
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/html-tailwind.csv +56 -56
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/jetpack-compose.csv +53 -53
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/nextjs.csv +53 -53
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/nuxt-ui.csv +51 -51
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/nuxtjs.csv +59 -59
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/react-native.csv +56 -56
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/react.csv +54 -54
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/shadcn.csv +61 -61
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/svelte.csv +54 -54
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/swiftui.csv +51 -51
- package/src/.agents/skills/ui-ux-pro-custom/data/stacks/vue.csv +50 -50
- package/src/.agents/skills/ui-ux-pro-custom/data/styles.csv +68 -68
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/alarmkit/SKILL.md +438 -438
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/alarmkit/references/alarmkit-patterns.md +584 -584
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-clips/SKILL.md +436 -436
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-intents/SKILL.md +489 -489
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-intents/references/appintents-advanced.md +1076 -1076
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-store-review/SKILL.md +340 -340
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-store-review/references/privacy-manifest.md +90 -90
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/app-store-review/references/review-checklists.md +106 -106
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/apple-on-device-ai/SKILL.md +500 -500
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/apple-on-device-ai/references/coreml-conversion.md +425 -425
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/apple-on-device-ai/references/coreml-optimization.md +344 -344
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/apple-on-device-ai/references/foundation-models.md +508 -508
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/apple-on-device-ai/references/mlx-swift.md +285 -285
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/authentication/SKILL.md +496 -496
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/authentication/references/keychain-biometric.md +211 -211
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/background-processing/SKILL.md +499 -499
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/background-processing/references/background-task-patterns.md +390 -390
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/callkit-voip/SKILL.md +461 -461
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/callkit-voip/references/callkit-patterns.md +425 -425
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/cloudkit-sync/SKILL.md +492 -492
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/cloudkit-sync/references/cloudkit-patterns.md +461 -461
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/codable-patterns/SKILL.md +467 -467
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/contacts-framework/SKILL.md +425 -425
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/contacts-framework/references/contacts-patterns.md +409 -409
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-bluetooth/SKILL.md +491 -491
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-bluetooth/references/ble-patterns.md +435 -435
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-motion/SKILL.md +388 -388
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-motion/references/motion-patterns.md +405 -405
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-nfc/SKILL.md +495 -495
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/core-nfc/references/nfc-patterns.md +420 -420
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/coreml/SKILL.md +459 -459
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/coreml/references/coreml-swift-integration.md +765 -765
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/debugging-instruments/SKILL.md +422 -422
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/debugging-instruments/references/instruments-guide.md +387 -387
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/debugging-instruments/references/lldb-patterns.md +298 -298
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/device-integrity/SKILL.md +477 -477
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/energykit/SKILL.md +460 -460
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/energykit/references/energykit-patterns.md +541 -541
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/eventkit-calendar/SKILL.md +483 -483
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/eventkit-calendar/references/eventkit-patterns.md +326 -326
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/healthkit/SKILL.md +498 -498
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/healthkit/references/healthkit-patterns.md +602 -602
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/homekit-matter/SKILL.md +496 -496
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/homekit-matter/references/matter-commissioning.md +455 -455
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-accessibility/SKILL.md +301 -301
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-accessibility/references/a11y-patterns.md +140 -140
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-localization/SKILL.md +418 -418
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-localization/references/formatstyle-locale.md +627 -627
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-localization/references/string-catalogs.md +462 -462
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-networking/SKILL.md +441 -441
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-networking/references/background-websocket.md +862 -862
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-networking/references/lightweight-clients.md +93 -93
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-networking/references/network-framework.md +563 -563
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-networking/references/urlsession-patterns.md +1116 -1116
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-security/SKILL.md +496 -496
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-security/references/app-review-guidelines.md +174 -174
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-security/references/cryptokit-advanced.md +296 -296
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-security/references/file-storage-patterns.md +354 -354
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/ios-security/references/privacy-manifest.md +117 -117
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/live-activities/SKILL.md +500 -500
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/live-activities/references/live-activity-patterns.md +868 -868
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/mapkit-location/SKILL.md +485 -485
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/mapkit-location/references/corelocation-patterns.md +730 -730
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/mapkit-location/references/mapkit-patterns.md +748 -748
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/metrickit-diagnostics/SKILL.md +479 -479
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/musickit-audio/SKILL.md +395 -395
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/musickit-audio/references/musickit-patterns.md +363 -363
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/natural-language/SKILL.md +412 -412
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/natural-language/references/translation-patterns.md +311 -311
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/passkit-wallet/SKILL.md +398 -398
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/passkit-wallet/references/wallet-passes.md +254 -254
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/pencilkit-drawing/SKILL.md +387 -387
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/pencilkit-drawing/references/paperkit-integration.md +376 -376
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/pencilkit-drawing/references/pencilkit-patterns.md +302 -302
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/permissionkit/SKILL.md +446 -446
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/permissionkit/references/permissionkit-patterns.md +435 -435
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/photos-camera-media/SKILL.md +500 -500
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/photos-camera-media/references/av-playback.md +701 -701
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/photos-camera-media/references/camera-capture.md +774 -774
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/photos-camera-media/references/image-loading-caching.md +869 -869
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/photos-camera-media/references/photospicker-patterns.md +597 -597
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/push-notifications/SKILL.md +500 -500
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/push-notifications/references/notification-patterns.md +677 -677
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/push-notifications/references/rich-notifications.md +745 -745
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/realitykit-ar/SKILL.md +479 -479
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/realitykit-ar/references/realitykit-patterns.md +480 -480
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/shareplay-activities/SKILL.md +483 -483
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/shareplay-activities/references/shareplay-patterns.md +544 -544
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/speech-recognition/SKILL.md +485 -485
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/storekit/SKILL.md +478 -478
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/storekit/references/app-review-guidelines.md +58 -58
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/storekit/references/storekit-advanced.md +755 -755
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-charts/SKILL.md +487 -487
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-charts/references/charts-patterns.md +895 -895
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-concurrency/SKILL.md +408 -408
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-concurrency/references/approachable-concurrency.md +80 -80
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-concurrency/references/swift-6-2-concurrency.md +233 -233
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-concurrency/references/swiftui-concurrency.md +187 -187
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-concurrency/references/synchronization-primitives.md +341 -341
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-language/SKILL.md +498 -498
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-language/references/swift-patterns-extended.md +505 -505
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-testing/SKILL.md +467 -467
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swift-testing/references/testing-patterns.md +504 -504
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftdata/SKILL.md +334 -334
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftdata/references/core-data-coexistence.md +504 -504
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftdata/references/swiftdata-advanced.md +975 -975
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftdata/references/swiftdata-queries.md +675 -675
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-animation/SKILL.md +481 -481
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-animation/references/animation-advanced.md +804 -804
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-animation/references/core-animation-bridge.md +553 -553
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-gestures/SKILL.md +450 -450
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-gestures/references/gesture-patterns.md +425 -425
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-layout-components/SKILL.md +336 -336
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-layout-components/references/form.md +97 -97
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-layout-components/references/grids.md +69 -69
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-layout-components/references/list.md +99 -99
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-layout-components/references/scrollview.md +147 -147
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-liquid-glass/SKILL.md +325 -325
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-liquid-glass/references/liquid-glass.md +387 -387
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-navigation/SKILL.md +262 -262
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-navigation/references/deeplinks.md +207 -207
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-navigation/references/navigationstack.md +177 -177
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-navigation/references/sheets.md +169 -169
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-navigation/references/tabview.md +178 -178
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-patterns/SKILL.md +381 -381
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-patterns/references/architecture-patterns.md +486 -486
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-patterns/references/deprecated-migration.md +1097 -1097
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-patterns/references/design-polish.md +780 -780
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-patterns/references/platform-and-sharing.md +696 -696
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-performance/SKILL.md +491 -491
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +46 -46
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +29 -29
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-performance/references/understanding-hangs-in-your-app.md +33 -33
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-performance/references/understanding-improving-swiftui-performance.md +52 -52
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-uikit-interop/SKILL.md +428 -428
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-uikit-interop/references/hosting-migration.md +534 -534
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/swiftui-uikit-interop/references/representable-recipes.md +1133 -1133
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/tipkit/SKILL.md +494 -494
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/tipkit/references/tipkit-patterns.md +782 -782
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/SKILL.md +475 -475
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/references/vision-requests.md +736 -736
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/references/visionkit-scanner.md +738 -738
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/weatherkit/SKILL.md +410 -410
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/weatherkit/references/weatherkit-patterns.md +567 -567
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/widgetkit/SKILL.md +497 -497
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/widgetkit/references/widgetkit-advanced.md +871 -871
- package/src/.agents/skills/ui-ux-pro-custom/data/typography.csv +57 -57
- package/src/.agents/skills/ui-ux-pro-custom/data/ui-reasoning.csv +101 -101
- package/src/.agents/skills/ui-ux-pro-custom/data/ux-guidelines.csv +99 -99
- package/src/.agents/skills/ui-ux-pro-custom/data/web-interface.csv +31 -31
- package/src/.agents/skills/ui-ux-pro-custom/scripts/core.py +253 -253
- package/src/.agents/skills/ui-ux-pro-custom/scripts/design_system.py +1067 -1067
- package/src/.agents/skills/ui-ux-pro-custom/scripts/search.py +114 -114
- package/src/.agents/skills/ux-audit/SKILL.md +150 -150
- package/src/.agents/skills/websocket-engineer/SKILL.md +168 -168
- package/src/.agents/skills/websocket-engineer/references/alternatives.md +391 -391
- package/src/.agents/skills/websocket-engineer/references/patterns.md +400 -400
- package/src/.agents/skills/websocket-engineer/references/protocol.md +195 -195
- package/src/.agents/skills/websocket-engineer/references/scaling.md +333 -333
- package/src/.agents/skills/websocket-engineer/references/security.md +474 -474
- package/src/.agents/skills/writing-skills/SKILL.md +655 -655
- package/src/.agents/skills/writing-skills/anthropic-best-practices.md +1150 -1150
- package/src/.agents/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -189
- package/src/.agents/skills/writing-skills/graphviz-conventions.dot +171 -171
- package/src/.agents/skills/writing-skills/persuasion-principles.md +187 -187
- package/src/.agents/skills/writing-skills/render-graphs.js +168 -168
- package/src/.agents/skills/writing-skills/testing-skills-with-subagents.md +384 -384
- package/src/.claude/commands/bmad-track-compact.md +1 -1
- package/src/.claude/commands/bmad-track-extended.md +1 -1
- package/src/.claude/commands/bmad-track-large.md +1 -1
- package/src/.claude/commands/bmad-track-medium.md +1 -1
- package/src/.claude/commands/bmad-track-nano.md +1 -1
- package/src/.claude/commands/bmad-track-rv.md +1 -1
- package/src/.claude/commands/bmad-track-small.md +1 -1
- package/src/.claude/commands/master-orchestrator.md +15 -0
- package/src/_memory/config.yaml +11 -11
- package/src/_memory/master-orchestrator-sidecar/instructions.md +85 -32
- package/src/_memory/skills/nimbalyst-tracking/SKILL.md +103 -103
- package/src/_memory/skills/writing-skills/SKILL.md +655 -655
- package/src/bmb/agents/agent-builder.md +59 -59
- package/src/bmb/agents/module-builder.md +60 -60
- package/src/bmb/agents/workflow-builder.md +61 -61
- package/src/bmb/config.yaml +12 -12
- package/src/bmb/module-help.csv +13 -13
- package/src/bmb/workflows/agent/data/agent-architecture.md +258 -258
- package/src/bmb/workflows/agent/data/agent-compilation.md +185 -185
- package/src/bmb/workflows/agent/data/agent-menu-patterns.md +189 -189
- package/src/bmb/workflows/agent/data/agent-metadata.md +133 -133
- package/src/bmb/workflows/agent/data/agent-validation.md +111 -111
- package/src/bmb/workflows/agent/data/brainstorm-context.md +96 -96
- package/src/bmb/workflows/agent/data/communication-presets.csv +61 -61
- package/src/bmb/workflows/agent/data/critical-actions.md +75 -75
- package/src/bmb/workflows/agent/data/persona-properties.md +252 -252
- package/src/bmb/workflows/agent/data/principles-crafting.md +142 -142
- package/src/bmb/workflows/agent/data/reference/module-examples/architect.md +68 -68
- package/src/bmb/workflows/agent/data/reference/with-sidecar/journal-keeper/journal-keeper-sidecar/entries/yy-mm-dd-entry-template.md +16 -16
- package/src/bmb/workflows/agent/data/understanding-agent-types.md +126 -126
- package/src/bmb/workflows/agent/steps-c/step-01-brainstorm.md +129 -129
- package/src/bmb/workflows/agent/steps-c/step-02-discovery.md +170 -170
- package/src/bmb/workflows/agent/steps-c/step-03-sidecar-metadata.md +309 -309
- package/src/bmb/workflows/agent/steps-c/step-04-persona.md +213 -213
- package/src/bmb/workflows/agent/steps-c/step-05-commands-menu.md +179 -179
- package/src/bmb/workflows/agent/steps-c/step-06-activation.md +278 -278
- package/src/bmb/workflows/agent/steps-c/step-07-build-agent.md +316 -316
- package/src/bmb/workflows/agent/steps-c/step-08-celebrate.md +247 -247
- package/src/bmb/workflows/agent/steps-e/e-01-load-existing.md +221 -221
- package/src/bmb/workflows/agent/steps-e/e-02-discover-edits.md +195 -195
- package/src/bmb/workflows/agent/steps-e/e-04-sidecar-metadata.md +126 -126
- package/src/bmb/workflows/agent/steps-e/e-05-persona.md +135 -135
- package/src/bmb/workflows/agent/steps-e/e-06-commands-menu.md +123 -123
- package/src/bmb/workflows/agent/steps-e/e-07-activation.md +124 -124
- package/src/bmb/workflows/agent/steps-e/e-08-edit-agent.md +197 -197
- package/src/bmb/workflows/agent/steps-e/e-09-celebrate.md +155 -155
- package/src/bmb/workflows/agent/steps-v/v-01-load-review.md +137 -137
- package/src/bmb/workflows/agent/steps-v/v-02a-validate-metadata.md +116 -116
- package/src/bmb/workflows/agent/steps-v/v-02b-validate-persona.md +124 -124
- package/src/bmb/workflows/agent/steps-v/v-02c-validate-menu.md +127 -127
- package/src/bmb/workflows/agent/steps-v/v-02d-validate-structure.md +134 -134
- package/src/bmb/workflows/agent/steps-v/v-02e-validate-sidecar.md +134 -134
- package/src/bmb/workflows/agent/steps-v/v-03-summary.md +104 -104
- package/src/bmb/workflows/agent/templates/agent-plan.template.md +5 -5
- package/src/bmb/workflows/agent/templates/agent-template.md +89 -89
- package/src/bmb/workflows/agent/workflow-create-agent.md +72 -72
- package/src/bmb/workflows/agent/workflow-edit-agent.md +75 -75
- package/src/bmb/workflows/agent/workflow-validate-agent.md +73 -73
- package/src/bmb/workflows/module/data/agent-architecture.md +179 -179
- package/src/bmb/workflows/module/data/agent-spec-template.md +79 -79
- package/src/bmb/workflows/module/data/module-standards.md +263 -263
- package/src/bmb/workflows/module/data/module-yaml-conventions.md +392 -392
- package/src/bmb/workflows/module/module-help-generate.md +254 -254
- package/src/bmb/workflows/module/steps-b/step-01-welcome.md +148 -148
- package/src/bmb/workflows/module/steps-b/step-02-spark.md +141 -141
- package/src/bmb/workflows/module/steps-b/step-03-module-type.md +149 -149
- package/src/bmb/workflows/module/steps-b/step-04-vision.md +83 -83
- package/src/bmb/workflows/module/steps-b/step-05-identity.md +97 -97
- package/src/bmb/workflows/module/steps-b/step-06-users.md +86 -86
- package/src/bmb/workflows/module/steps-b/step-07-value.md +76 -76
- package/src/bmb/workflows/module/steps-b/step-08-agents.md +97 -97
- package/src/bmb/workflows/module/steps-b/step-09-workflows.md +83 -83
- package/src/bmb/workflows/module/steps-b/step-10-tools.md +91 -91
- package/src/bmb/workflows/module/steps-b/step-11-scenarios.md +84 -84
- package/src/bmb/workflows/module/steps-b/step-12-creative.md +95 -95
- package/src/bmb/workflows/module/steps-b/step-13-review.md +105 -105
- package/src/bmb/workflows/module/steps-b/step-14-finalize.md +117 -117
- package/src/bmb/workflows/module/steps-c/step-01-load-brief.md +179 -179
- package/src/bmb/workflows/module/steps-c/step-01b-continue.md +82 -82
- package/src/bmb/workflows/module/steps-c/step-02-structure.md +105 -105
- package/src/bmb/workflows/module/steps-c/step-03-config.md +119 -119
- package/src/bmb/workflows/module/steps-c/step-04-agents.md +168 -168
- package/src/bmb/workflows/module/steps-c/step-05-workflows.md +184 -184
- package/src/bmb/workflows/module/steps-c/step-06-docs.md +401 -401
- package/src/bmb/workflows/module/steps-c/step-07-complete.md +152 -152
- package/src/bmb/workflows/module/steps-e/step-01-load-target.md +81 -81
- package/src/bmb/workflows/module/steps-e/step-02-select-edit.md +77 -77
- package/src/bmb/workflows/module/steps-e/step-03-apply-edit.md +77 -77
- package/src/bmb/workflows/module/steps-e/step-04-review.md +80 -80
- package/src/bmb/workflows/module/steps-e/step-05-confirm.md +75 -75
- package/src/bmb/workflows/module/steps-v/step-01-load-target.md +96 -96
- package/src/bmb/workflows/module/steps-v/step-02-file-structure.md +93 -93
- package/src/bmb/workflows/module/steps-v/step-03-module-yaml.md +99 -99
- package/src/bmb/workflows/module/steps-v/step-04-agent-specs.md +152 -152
- package/src/bmb/workflows/module/steps-v/step-05-workflow-specs.md +152 -152
- package/src/bmb/workflows/module/steps-v/step-06-documentation.md +143 -143
- package/src/bmb/workflows/module/steps-v/step-07-installation.md +102 -102
- package/src/bmb/workflows/module/steps-v/step-08-report.md +197 -197
- package/src/bmb/workflows/module/templates/brief-template.md +154 -154
- package/src/bmb/workflows/module/templates/workflow-spec-template.md +96 -96
- package/src/bmb/workflows/module/workflow-create-module-brief.md +71 -71
- package/src/bmb/workflows/module/workflow-create-module.md +86 -86
- package/src/bmb/workflows/module/workflow-edit-module.md +66 -66
- package/src/bmb/workflows/module/workflow-validate-module.md +66 -66
- package/src/bmb/workflows/workflow/data/architecture.md +150 -150
- package/src/bmb/workflows/workflow/data/common-workflow-tools.csv +19 -19
- package/src/bmb/workflows/workflow/data/csv-data-file-standards.md +53 -53
- package/src/bmb/workflows/workflow/data/frontmatter-standards.md +184 -184
- package/src/bmb/workflows/workflow/data/input-discovery-standards.md +191 -191
- package/src/bmb/workflows/workflow/data/intent-vs-prescriptive-spectrum.md +44 -44
- package/src/bmb/workflows/workflow/data/menu-handling-standards.md +133 -133
- package/src/bmb/workflows/workflow/data/output-format-standards.md +135 -135
- package/src/bmb/workflows/workflow/data/step-file-rules.md +235 -235
- package/src/bmb/workflows/workflow/data/step-type-patterns.md +257 -257
- package/src/bmb/workflows/workflow/data/subprocess-optimization-patterns.md +188 -188
- package/src/bmb/workflows/workflow/data/trimodal-workflow-structure.md +164 -164
- package/src/bmb/workflows/workflow/data/workflow-chaining-standards.md +222 -222
- package/src/bmb/workflows/workflow/data/workflow-examples.md +232 -232
- package/src/bmb/workflows/workflow/data/workflow-type-criteria.md +134 -134
- package/src/bmb/workflows/workflow/steps-c/step-00-conversion.md +263 -263
- package/src/bmb/workflows/workflow/steps-c/step-01-discovery.md +194 -194
- package/src/bmb/workflows/workflow/steps-c/step-01b-continuation.md +3 -3
- package/src/bmb/workflows/workflow/steps-c/step-02-classification.md +270 -270
- package/src/bmb/workflows/workflow/steps-c/step-03-requirements.md +283 -283
- package/src/bmb/workflows/workflow/steps-c/step-04-tools.md +282 -282
- package/src/bmb/workflows/workflow/steps-c/step-05-plan-review.md +243 -243
- package/src/bmb/workflows/workflow/steps-c/step-06-design.md +330 -330
- package/src/bmb/workflows/workflow/steps-c/step-07-foundation.md +239 -239
- package/src/bmb/workflows/workflow/steps-c/step-08-build-step-01.md +379 -379
- package/src/bmb/workflows/workflow/steps-c/step-09-build-next-step.md +350 -350
- package/src/bmb/workflows/workflow/steps-c/step-10-confirmation.md +322 -322
- package/src/bmb/workflows/workflow/steps-c/step-11-completion.md +191 -191
- package/src/bmb/workflows/workflow/steps-e/step-e-01-assess-workflow.md +237 -237
- package/src/bmb/workflows/workflow/steps-e/step-e-02-discover-edits.md +251 -251
- package/src/bmb/workflows/workflow/steps-e/step-e-03-fix-validation.md +254 -254
- package/src/bmb/workflows/workflow/steps-e/step-e-04-direct-edit.md +277 -277
- package/src/bmb/workflows/workflow/steps-e/step-e-05-apply-edit.md +154 -154
- package/src/bmb/workflows/workflow/steps-e/step-e-06-validate-after.md +190 -190
- package/src/bmb/workflows/workflow/steps-e/step-e-07-complete.md +206 -206
- package/src/bmb/workflows/workflow/steps-v/step-01-validate-max-mode.md +109 -109
- package/src/bmb/workflows/workflow/steps-v/step-01-validate.md +221 -221
- package/src/bmb/workflows/workflow/steps-v/step-01b-structure.md +152 -152
- package/src/bmb/workflows/workflow/steps-v/step-02-frontmatter-validation.md +199 -199
- package/src/bmb/workflows/workflow/steps-v/step-02b-path-violations.md +265 -265
- package/src/bmb/workflows/workflow/steps-v/step-03-menu-validation.md +164 -164
- package/src/bmb/workflows/workflow/steps-v/step-04-step-type-validation.md +211 -211
- package/src/bmb/workflows/workflow/steps-v/step-05-output-format-validation.md +200 -200
- package/src/bmb/workflows/workflow/steps-v/step-06-validation-design-check.md +195 -195
- package/src/bmb/workflows/workflow/steps-v/step-07-instruction-style-check.md +209 -209
- package/src/bmb/workflows/workflow/steps-v/step-08-collaborative-experience-check.md +199 -199
- package/src/bmb/workflows/workflow/steps-v/step-08b-subprocess-optimization.md +179 -179
- package/src/bmb/workflows/workflow/steps-v/step-09-cohesive-review.md +186 -186
- package/src/bmb/workflows/workflow/steps-v/step-10-report-complete.md +154 -154
- package/src/bmb/workflows/workflow/steps-v/step-11-plan-validation.md +237 -237
- package/src/bmb/workflows/workflow/templates/minimal-output-template.md +11 -11
- package/src/bmb/workflows/workflow/templates/step-01-init-continuable-template.md +241 -241
- package/src/bmb/workflows/workflow/templates/step-1b-template.md +224 -224
- package/src/bmb/workflows/workflow/templates/step-template.md +294 -294
- package/src/bmb/workflows/workflow/templates/workflow-template.md +102 -102
- package/src/bmb/workflows/workflow/workflow-create-workflow.md +79 -79
- package/src/bmb/workflows/workflow/workflow-edit-workflow.md +65 -65
- package/src/bmb/workflows/workflow/workflow-rework-workflow.md +65 -65
- package/src/bmb/workflows/workflow/workflow-validate-max-parallel-workflow.md +66 -66
- package/src/bmb/workflows/workflow/workflow-validate-workflow.md +65 -65
- package/src/bmm/agents/analyst.md +104 -104
- package/src/bmm/agents/dev.md +100 -100
- package/src/bmm/agents/qa.md +100 -90
- package/src/bmm/agents/review-agent.md +1 -1
- package/src/bmm/agents/tech-writer/tech-writer.md +94 -94
- package/src/bmm/module-help.csv +31 -31
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-01-init.md +115 -115
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-01b-continue.md +107 -107
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-02-vision.md +141 -141
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-03-users.md +144 -144
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-04-metrics.md +147 -147
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-05-scope.md +161 -161
- package/src/bmm/workflows/1-analysis/create-product-brief/steps/step-06-complete.md +99 -99
- package/src/bmm/workflows/1-analysis/create-product-brief/workflow.md +57 -57
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-01-init.md +87 -87
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-02-domain-analysis.md +156 -156
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-03-competitive-landscape.md +165 -165
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-04-regulatory-focus.md +140 -140
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-05-technical-trends.md +152 -152
- package/src/bmm/workflows/1-analysis/research/domain-steps/step-06-research-synthesis.md +345 -345
- package/src/bmm/workflows/1-analysis/research/market-steps/step-01-init.md +92 -92
- package/src/bmm/workflows/1-analysis/research/market-steps/step-02-customer-behavior.md +164 -164
- package/src/bmm/workflows/1-analysis/research/market-steps/step-03-customer-pain-points.md +174 -174
- package/src/bmm/workflows/1-analysis/research/market-steps/step-04-customer-decisions.md +184 -184
- package/src/bmm/workflows/1-analysis/research/market-steps/step-05-competitive-analysis.md +105 -105
- package/src/bmm/workflows/1-analysis/research/market-steps/step-06-research-completion.md +360 -360
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-01-init.md +87 -87
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-02-technical-overview.md +165 -165
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-03-integration-patterns.md +174 -174
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-04-architectural-patterns.md +141 -141
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-05-implementation-research.md +159 -159
- package/src/bmm/workflows/1-analysis/research/technical-steps/step-06-research-synthesis.md +387 -387
- package/src/bmm/workflows/1-analysis/research/workflow-domain-research.md +54 -54
- package/src/bmm/workflows/1-analysis/research/workflow-market-research.md +54 -54
- package/src/bmm/workflows/1-analysis/research/workflow-technical-research.md +54 -54
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-01b-continue.md +100 -100
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02-discovery.md +160 -160
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02b-vision.md +88 -88
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-02c-executive-summary.md +99 -99
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-03-success.md +169 -169
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-04-journeys.md +156 -156
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-05-domain.md +136 -136
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-06-innovation.md +176 -176
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-07-project-type.md +184 -184
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-08-scoping.md +174 -174
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-09-functional.md +175 -175
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-10-nonfunctional.md +189 -189
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-11-polish.md +162 -162
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-c/step-12-complete.md +79 -79
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-01-discovery.md +183 -183
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-01b-legacy-conversion.md +149 -149
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-02-review.md +187 -187
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-03-edit.md +192 -192
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-e/step-e-04-complete.md +108 -108
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-01-discovery.md +166 -166
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-02-format-detection.md +131 -131
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-02b-parity-check.md +150 -150
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-03-density-validation.md +118 -118
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-04-brief-coverage-validation.md +155 -155
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-05-measurability-validation.md +170 -170
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-06-traceability-validation.md +158 -158
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-07-implementation-leakage-validation.md +147 -147
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-08-domain-compliance-validation.md +182 -182
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-09-project-type-validation.md +202 -202
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-10-smart-validation.md +148 -148
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-11-holistic-quality-validation.md +201 -201
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-12-completeness-validation.md +179 -179
- package/src/bmm/workflows/2-plan-workflows/create-prd/steps-v/step-v-13-report-complete.md +164 -164
- package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-create-prd.md +65 -65
- package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-edit-prd.md +65 -65
- package/src/bmm/workflows/2-plan-workflows/create-prd/workflow-validate-prd.md +63 -63
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-01b-continue.md +63 -63
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-02-discovery.md +106 -106
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-03-core-experience.md +111 -111
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-04-emotional-response.md +115 -115
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-05-inspiration.md +127 -127
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-06-design-system.md +167 -167
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-07-defining-experience.md +143 -143
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-08-visual-foundation.md +118 -118
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-09-design-directions.md +154 -154
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-10-user-journeys.md +136 -136
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-11-component-strategy.md +165 -165
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-12-ux-patterns.md +135 -135
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-13-responsive-accessibility.md +192 -192
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/steps/step-14-complete.md +101 -101
- package/src/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md +45 -45
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-01-document-discovery.md +185 -185
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-02-prd-analysis.md +129 -129
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-03-epic-coverage-validation.md +130 -130
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-04-ux-alignment.md +93 -93
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-05-epic-quality-review.md +196 -196
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/steps/step-06-final-assessment.md +129 -129
- package/src/bmm/workflows/3-solutioning/check-implementation-readiness/workflow.md +54 -54
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-01b-continue.md +82 -82
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-02-context.md +106 -106
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-03-starter.md +138 -138
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-04-decisions.md +129 -129
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-05-patterns.md +166 -166
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-06-structure.md +186 -186
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-07-validation.md +163 -163
- package/src/bmm/workflows/3-solutioning/create-architecture/steps/step-08-complete.md +38 -38
- package/src/bmm/workflows/3-solutioning/create-architecture/workflow.md +49 -49
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-02-design-epics.md +124 -124
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-03-create-stories.md +122 -122
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/steps/step-04-final-validation.md +84 -84
- package/src/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md +58 -58
- package/src/bmm/workflows/4-implementation/code-review/workflow.yaml +43 -43
- package/src/bmm/workflows/4-implementation/correct-course/workflow.yaml +53 -53
- package/src/bmm/workflows/4-implementation/create-story/checklist.md +159 -159
- package/src/bmm/workflows/4-implementation/create-story/template.md +79 -79
- package/src/bmm/workflows/4-implementation/create-story/workflow.yaml +52 -52
- package/src/bmm/workflows/4-implementation/dev-story/workflow.yaml +20 -20
- package/src/bmm/workflows/4-implementation/retrospective/workflow.yaml +52 -52
- package/src/bmm/workflows/4-implementation/sprint-planning/workflow.yaml +52 -52
- package/src/bmm/workflows/4-implementation/sprint-status/workflow.yaml +25 -25
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-01-mode-detection.md +158 -158
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-02-context-gathering.md +122 -122
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-03-execute.md +93 -93
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-04-self-check.md +93 -93
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-05-adversarial-review.md +87 -87
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/steps/step-06-resolve-findings.md +146 -146
- package/src/bmm/workflows/bmad-quick-flow/quick-dev/workflow.md +50 -50
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-02-investigate.md +152 -152
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-03-generate.md +123 -123
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/steps/step-04-review.md +201 -201
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/workflow.md +79 -79
- package/src/bmm/workflows/document-project/workflow.yaml +22 -22
- package/src/bmm/workflows/generate-project-context/steps/step-01-discover.md +184 -184
- package/src/bmm/workflows/generate-project-context/steps/step-02-generate.md +322 -322
- package/src/bmm/workflows/generate-project-context/steps/step-03-complete.md +235 -235
- package/src/bmm/workflows/generate-project-context/workflow.md +49 -49
- package/src/bmm/workflows/qa/automate/workflow.yaml +233 -233
- package/src/bmm/workflows/qa-generate-e2e-tests/workflow.yaml +42 -42
- package/src/core/agents/master-orchestrator.md +3 -3
- package/src/core/config.yaml +9 -9
- package/src/core/module-help.csv +10 -10
- package/src/core/scripts/generate-loop-report.py +72 -72
- package/src/core/tasks/editorial-review-prose.xml +101 -101
- package/src/core/tasks/editorial-review-structure.xml +207 -207
- package/src/core/tasks/help.md +86 -86
- package/src/core/tasks/index-docs.xml +64 -64
- package/src/core/tasks/review-adversarial-general.xml +66 -66
- package/src/core/tasks/review-adversarial-loop.xml +46 -46
- package/src/core/tasks/review-edge-case-hunter.xml +63 -63
- package/src/core/tasks/review-party-loop.xml +46 -46
- package/src/core/tasks/shard-doc.xml +107 -107
- package/src/core/tasks/workflow.xml +235 -235
- package/src/core/templates/review-loop-report.html +88 -88
- package/src/core/templates/review-loop-report.md +5 -5
- package/src/core/workflows/advanced-elicitation/workflow.xml +117 -117
- package/src/core/workflows/brainstorming/steps/step-01-session-setup.md +212 -212
- package/src/core/workflows/brainstorming/steps/step-01b-continue.md +122 -122
- package/src/core/workflows/brainstorming/steps/step-02a-user-selected.md +225 -225
- package/src/core/workflows/brainstorming/steps/step-02b-ai-recommended.md +237 -237
- package/src/core/workflows/brainstorming/steps/step-02c-random-selection.md +209 -209
- package/src/core/workflows/brainstorming/steps/step-02d-progressive-flow.md +264 -264
- package/src/core/workflows/brainstorming/steps/step-02e-deep-dive.md +68 -68
- package/src/core/workflows/brainstorming/steps/step-03-technique-execution.md +403 -403
- package/src/core/workflows/brainstorming/steps/step-04-idea-organization.md +303 -303
- package/src/core/workflows/brainstorming/workflow.md +60 -60
- package/src/core/workflows/extract-trackers/workflow.md +45 -45
- package/src/core/workflows/party-mode/steps/step-01-agent-loading.md +142 -142
- package/src/core/workflows/party-mode/workflow.md +194 -194
- package/src/docs/dev/tmux/actions_popup.py +291 -291
- package/src/docs/dev/tmux/tmux-setup.md +62 -1
|
@@ -1,868 +1,868 @@
|
|
|
1
|
-
# Live Activity Patterns
|
|
2
|
-
|
|
3
|
-
Complete implementation patterns for ActivityKit Live Activities, Dynamic
|
|
4
|
-
Island, push-to-update, and lifecycle management. All patterns use modern
|
|
5
|
-
Swift async/await and target iOS 16.1+ unless noted.
|
|
6
|
-
|
|
7
|
-
## Contents
|
|
8
|
-
|
|
9
|
-
- [Complete ActivityAttributes and ContentState](#complete-activityattributes-and-contentstate)
|
|
10
|
-
- [Starting a Live Activity with All Parameters](#starting-a-live-activity-with-all-parameters)
|
|
11
|
-
- [Updating from the App](#updating-from-the-app)
|
|
12
|
-
- [Push-to-Update Server Payload Format](#push-to-update-server-payload-format)
|
|
13
|
-
- [Ending with Different Dismissal Policies](#ending-with-different-dismissal-policies)
|
|
14
|
-
- [Complete Dynamic Island Layout (All Regions)](#complete-dynamic-island-layout-all-regions)
|
|
15
|
-
- [Lock Screen Layout with Timer and Progress](#lock-screen-layout-with-timer-and-progress)
|
|
16
|
-
- [Multiple Concurrent Activities](#multiple-concurrent-activities)
|
|
17
|
-
- [Observing Activity State Changes](#observing-activity-state-changes)
|
|
18
|
-
- [Token Update Handling](#token-update-handling)
|
|
19
|
-
- [Authorization Check](#authorization-check)
|
|
20
|
-
- [Error Handling](#error-handling)
|
|
21
|
-
- [Background Handling Considerations](#background-handling-considerations)
|
|
22
|
-
- [Testing in Simulator and on Device](#testing-in-simulator-and-on-device)
|
|
23
|
-
- [Info.plist Keys Reference](#infoplist-keys-reference)
|
|
24
|
-
- [Apple Documentation Links](#apple-documentation-links)
|
|
25
|
-
|
|
26
|
-
## Complete ActivityAttributes and ContentState
|
|
27
|
-
|
|
28
|
-
Define the data model for your Live Activity. Static properties go on the
|
|
29
|
-
outer struct; dynamic properties go in `ContentState`.
|
|
30
|
-
|
|
31
|
-
```swift
|
|
32
|
-
import ActivityKit
|
|
33
|
-
|
|
34
|
-
struct RideAttributes: ActivityAttributes {
|
|
35
|
-
// Static -- set at creation, immutable for the activity lifetime
|
|
36
|
-
var riderName: String
|
|
37
|
-
var pickupLocation: String
|
|
38
|
-
var dropoffLocation: String
|
|
39
|
-
|
|
40
|
-
struct ContentState: Codable, Hashable {
|
|
41
|
-
var driverName: String
|
|
42
|
-
var driverPhoto: String // SF Symbol name or asset name
|
|
43
|
-
var vehicleDescription: String
|
|
44
|
-
var eta: ClosedRange<Date>
|
|
45
|
-
var status: RideStatus
|
|
46
|
-
var distanceRemaining: Double // miles
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
enum RideStatus: String, Codable, Hashable {
|
|
51
|
-
case driverAssigned
|
|
52
|
-
case driverEnRoute
|
|
53
|
-
case driverArrived
|
|
54
|
-
case inProgress
|
|
55
|
-
case arriving
|
|
56
|
-
case completed
|
|
57
|
-
case cancelled
|
|
58
|
-
}
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
Keep `ContentState` lightweight. Every update serializes the entire struct, and
|
|
62
|
-
push payloads have a 4 KB size limit. Avoid storing images, large strings, or
|
|
63
|
-
deeply nested objects.
|
|
64
|
-
|
|
65
|
-
## Starting a Live Activity with All Parameters
|
|
66
|
-
|
|
67
|
-
```swift
|
|
68
|
-
import ActivityKit
|
|
69
|
-
|
|
70
|
-
@MainActor
|
|
71
|
-
func startRideActivity(
|
|
72
|
-
rider: String,
|
|
73
|
-
pickup: String,
|
|
74
|
-
dropoff: String,
|
|
75
|
-
driver: String,
|
|
76
|
-
vehicle: String
|
|
77
|
-
) async throws -> Activity<RideAttributes> {
|
|
78
|
-
// Check authorization before attempting to start
|
|
79
|
-
let authInfo = ActivityAuthorizationInfo()
|
|
80
|
-
guard authInfo.areActivitiesEnabled else {
|
|
81
|
-
throw RideError.liveActivitiesDisabled
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
let attributes = RideAttributes(
|
|
85
|
-
riderName: rider,
|
|
86
|
-
pickupLocation: pickup,
|
|
87
|
-
dropoffLocation: dropoff
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
let initialState = RideAttributes.ContentState(
|
|
91
|
-
driverName: driver,
|
|
92
|
-
driverPhoto: "car.fill",
|
|
93
|
-
vehicleDescription: vehicle,
|
|
94
|
-
eta: Date()...Date().addingTimeInterval(600),
|
|
95
|
-
status: .driverAssigned,
|
|
96
|
-
distanceRemaining: 2.5
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
let content = ActivityContent(
|
|
100
|
-
state: initialState,
|
|
101
|
-
staleDate: Date().addingTimeInterval(120), // stale after 2 min
|
|
102
|
-
relevanceScore: 80
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
let activity = try Activity.request(
|
|
106
|
-
attributes: attributes,
|
|
107
|
-
content: content,
|
|
108
|
-
pushType: .token // Enable push updates
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
// Forward push token to server for remote updates
|
|
112
|
-
Task {
|
|
113
|
-
for await token in activity.pushTokenUpdates {
|
|
114
|
-
let tokenString = token.map { String(format: "%02x", $0) }.joined()
|
|
115
|
-
try? await ServerAPI.shared.registerActivityToken(
|
|
116
|
-
tokenString, rideID: activity.id
|
|
117
|
-
)
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Observe state changes for cleanup
|
|
122
|
-
Task {
|
|
123
|
-
for await state in activity.activityStateUpdates {
|
|
124
|
-
if state == .dismissed {
|
|
125
|
-
// Activity removed from UI -- clean up local resources
|
|
126
|
-
RideStore.shared.removeActivity(id: activity.id)
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return activity
|
|
132
|
-
}
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### Starting with Scheduled Date (iOS 26+)
|
|
136
|
-
|
|
137
|
-
Schedule the activity to appear at a future time without the app in foreground:
|
|
138
|
-
|
|
139
|
-
```swift
|
|
140
|
-
let gameTime = Calendar.current.date(
|
|
141
|
-
from: DateComponents(year: 2026, month: 3, day: 15, hour: 19, minute: 0)
|
|
142
|
-
)!
|
|
143
|
-
|
|
144
|
-
let activity = try Activity.request(
|
|
145
|
-
attributes: attributes,
|
|
146
|
-
content: content,
|
|
147
|
-
pushType: .token,
|
|
148
|
-
start: gameTime // iOS 26+
|
|
149
|
-
)
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### Starting with ActivityStyle (iOS 16.1+ type, `style:` parameter iOS 26+)
|
|
153
|
-
|
|
154
|
-
```swift
|
|
155
|
-
// Transient: system may auto-dismiss after a period
|
|
156
|
-
let activity = try Activity.request(
|
|
157
|
-
attributes: attributes,
|
|
158
|
-
content: content,
|
|
159
|
-
pushType: .token,
|
|
160
|
-
style: .transient // style: parameter requires iOS 26+
|
|
161
|
-
)
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
## Updating from the App
|
|
165
|
-
|
|
166
|
-
```swift
|
|
167
|
-
func updateRideActivity(
|
|
168
|
-
_ activity: Activity<RideAttributes>,
|
|
169
|
-
newStatus: RideStatus,
|
|
170
|
-
eta: ClosedRange<Date>,
|
|
171
|
-
distance: Double,
|
|
172
|
-
showAlert: Bool = false
|
|
173
|
-
) async {
|
|
174
|
-
let updatedState = RideAttributes.ContentState(
|
|
175
|
-
driverName: activity.content.state.driverName,
|
|
176
|
-
driverPhoto: activity.content.state.driverPhoto,
|
|
177
|
-
vehicleDescription: activity.content.state.vehicleDescription,
|
|
178
|
-
eta: eta,
|
|
179
|
-
status: newStatus,
|
|
180
|
-
distanceRemaining: distance
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
let content = ActivityContent(
|
|
184
|
-
state: updatedState,
|
|
185
|
-
staleDate: Date().addingTimeInterval(120),
|
|
186
|
-
relevanceScore: newStatus == .driverArrived ? 100 : 80
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
if showAlert {
|
|
190
|
-
await activity.update(content, alertConfiguration: AlertConfiguration(
|
|
191
|
-
title: "Ride Update",
|
|
192
|
-
body: alertMessage(for: newStatus),
|
|
193
|
-
sound: .default
|
|
194
|
-
))
|
|
195
|
-
} else {
|
|
196
|
-
await activity.update(content)
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
private func alertMessage(for status: RideStatus) -> String {
|
|
201
|
-
switch status {
|
|
202
|
-
case .driverArrived: "Your driver has arrived!"
|
|
203
|
-
case .arriving: "You're almost there!"
|
|
204
|
-
case .completed: "You've arrived at your destination."
|
|
205
|
-
default: "Your ride status has changed."
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
## Push-to-Update Server Payload Format
|
|
211
|
-
|
|
212
|
-
### Update Payload
|
|
213
|
-
|
|
214
|
-
```json
|
|
215
|
-
{
|
|
216
|
-
"aps": {
|
|
217
|
-
"timestamp": 1700000000,
|
|
218
|
-
"event": "update",
|
|
219
|
-
"content-state": {
|
|
220
|
-
"driverName": "Maria",
|
|
221
|
-
"driverPhoto": "car.fill",
|
|
222
|
-
"vehicleDescription": "White Toyota Camry",
|
|
223
|
-
"eta": {
|
|
224
|
-
"lowerBound": 1700000000,
|
|
225
|
-
"upperBound": 1700000300
|
|
226
|
-
},
|
|
227
|
-
"status": "driverArrived",
|
|
228
|
-
"distanceRemaining": 0.0
|
|
229
|
-
},
|
|
230
|
-
"stale-date": 1700000300,
|
|
231
|
-
"relevance-score": 100,
|
|
232
|
-
"alert": {
|
|
233
|
-
"title": "Ride Update",
|
|
234
|
-
"body": "Your driver has arrived!",
|
|
235
|
-
"sound": "default"
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
### End Payload
|
|
242
|
-
|
|
243
|
-
```json
|
|
244
|
-
{
|
|
245
|
-
"aps": {
|
|
246
|
-
"timestamp": 1700002000,
|
|
247
|
-
"event": "end",
|
|
248
|
-
"dismissal-date": 1700005600,
|
|
249
|
-
"content-state": {
|
|
250
|
-
"driverName": "Maria",
|
|
251
|
-
"driverPhoto": "car.fill",
|
|
252
|
-
"vehicleDescription": "White Toyota Camry",
|
|
253
|
-
"eta": {
|
|
254
|
-
"lowerBound": 1700002000,
|
|
255
|
-
"upperBound": 1700002000
|
|
256
|
-
},
|
|
257
|
-
"status": "completed",
|
|
258
|
-
"distanceRemaining": 0.0
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
### Push-to-Start Payload (iOS 17.2+)
|
|
265
|
-
|
|
266
|
-
Send to the push-to-start token to remotely create an activity:
|
|
267
|
-
|
|
268
|
-
```json
|
|
269
|
-
{
|
|
270
|
-
"aps": {
|
|
271
|
-
"timestamp": 1700000000,
|
|
272
|
-
"event": "start",
|
|
273
|
-
"attributes-type": "RideAttributes",
|
|
274
|
-
"attributes": {
|
|
275
|
-
"riderName": "Jordan",
|
|
276
|
-
"pickupLocation": "123 Main St",
|
|
277
|
-
"dropoffLocation": "456 Oak Ave"
|
|
278
|
-
},
|
|
279
|
-
"content-state": {
|
|
280
|
-
"driverName": "Maria",
|
|
281
|
-
"driverPhoto": "car.fill",
|
|
282
|
-
"vehicleDescription": "White Toyota Camry",
|
|
283
|
-
"eta": {
|
|
284
|
-
"lowerBound": 1700000000,
|
|
285
|
-
"upperBound": 1700000600
|
|
286
|
-
},
|
|
287
|
-
"status": "driverAssigned",
|
|
288
|
-
"distanceRemaining": 3.2
|
|
289
|
-
},
|
|
290
|
-
"alert": {
|
|
291
|
-
"title": "Ride Matched",
|
|
292
|
-
"body": "Maria is on the way in a White Toyota Camry."
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
### Required APNs HTTP Headers
|
|
299
|
-
|
|
300
|
-
| Header | Value |
|
|
301
|
-
|---|---|
|
|
302
|
-
| `apns-push-type` | `liveactivity` |
|
|
303
|
-
| `apns-topic` | `<bundle-id>.push-type.liveactivity` |
|
|
304
|
-
| `apns-priority` | `5` (silent) or `10` (alert, updates with sound/banner) |
|
|
305
|
-
| `authorization` | `bearer <jwt>` (token auth) or use certificate auth |
|
|
306
|
-
|
|
307
|
-
The `content-state` JSON keys and value types must match the `ContentState`
|
|
308
|
-
Codable representation exactly. A type mismatch (e.g., sending a string where
|
|
309
|
-
a number is expected) causes the update to fail silently.
|
|
310
|
-
|
|
311
|
-
## Ending with Different Dismissal Policies
|
|
312
|
-
|
|
313
|
-
```swift
|
|
314
|
-
func endRideActivity(
|
|
315
|
-
_ activity: Activity<RideAttributes>,
|
|
316
|
-
finalStatus: RideStatus
|
|
317
|
-
) async {
|
|
318
|
-
let finalState = RideAttributes.ContentState(
|
|
319
|
-
driverName: activity.content.state.driverName,
|
|
320
|
-
driverPhoto: activity.content.state.driverPhoto,
|
|
321
|
-
vehicleDescription: activity.content.state.vehicleDescription,
|
|
322
|
-
eta: Date()...Date(),
|
|
323
|
-
status: finalStatus,
|
|
324
|
-
distanceRemaining: 0
|
|
325
|
-
)
|
|
326
|
-
|
|
327
|
-
let content = ActivityContent(state: finalState, staleDate: nil, relevanceScore: 0)
|
|
328
|
-
|
|
329
|
-
switch finalStatus {
|
|
330
|
-
case .completed:
|
|
331
|
-
// Keep on Lock Screen for 1 hour so user can review trip details
|
|
332
|
-
await activity.end(content, dismissalPolicy: .after(
|
|
333
|
-
Date().addingTimeInterval(3600)
|
|
334
|
-
))
|
|
335
|
-
case .cancelled:
|
|
336
|
-
// Remove immediately -- no useful info to show
|
|
337
|
-
await activity.end(content, dismissalPolicy: .immediate)
|
|
338
|
-
default:
|
|
339
|
-
// Let the system decide
|
|
340
|
-
await activity.end(content, dismissalPolicy: .default)
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
### Ending All Activities (cleanup on sign-out)
|
|
346
|
-
|
|
347
|
-
```swift
|
|
348
|
-
func endAllRideActivities() async {
|
|
349
|
-
for activity in Activity<RideAttributes>.activities {
|
|
350
|
-
await activity.end(nil, dismissalPolicy: .immediate)
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
## Complete Dynamic Island Layout (All Regions)
|
|
356
|
-
|
|
357
|
-
```swift
|
|
358
|
-
struct RideActivityWidget: Widget {
|
|
359
|
-
var body: some WidgetConfiguration {
|
|
360
|
-
ActivityConfiguration(for: RideAttributes.self) { context in
|
|
361
|
-
// Lock Screen presentation
|
|
362
|
-
RideLockScreenView(context: context)
|
|
363
|
-
} dynamicIsland: { context in
|
|
364
|
-
DynamicIsland {
|
|
365
|
-
// EXPANDED: shown on long-press
|
|
366
|
-
DynamicIslandExpandedRegion(.leading) {
|
|
367
|
-
VStack(alignment: .leading, spacing: 4) {
|
|
368
|
-
Image(systemName: context.state.driverPhoto)
|
|
369
|
-
.font(.title2)
|
|
370
|
-
Text(context.state.driverName)
|
|
371
|
-
.font(.caption2)
|
|
372
|
-
.lineLimit(1)
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
DynamicIslandExpandedRegion(.trailing) {
|
|
377
|
-
VStack(alignment: .trailing, spacing: 4) {
|
|
378
|
-
Text(timerInterval: context.state.eta, countsDown: true)
|
|
379
|
-
.font(.title3.monospacedDigit())
|
|
380
|
-
Text(String(format: "%.1f mi", context.state.distanceRemaining))
|
|
381
|
-
.font(.caption2)
|
|
382
|
-
.foregroundStyle(.secondary)
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
DynamicIslandExpandedRegion(.center) {
|
|
387
|
-
Text(context.state.status.displayName)
|
|
388
|
-
.font(.headline)
|
|
389
|
-
.lineLimit(1)
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
DynamicIslandExpandedRegion(.bottom) {
|
|
393
|
-
VStack(spacing: 8) {
|
|
394
|
-
ProgressView(
|
|
395
|
-
value: context.state.status.progress,
|
|
396
|
-
total: 1.0
|
|
397
|
-
)
|
|
398
|
-
.tint(.green)
|
|
399
|
-
|
|
400
|
-
HStack {
|
|
401
|
-
Label(context.attributes.pickupLocation,
|
|
402
|
-
systemImage: "mappin.circle.fill")
|
|
403
|
-
Spacer()
|
|
404
|
-
Label(context.attributes.dropoffLocation,
|
|
405
|
-
systemImage: "flag.checkered")
|
|
406
|
-
}
|
|
407
|
-
.font(.caption2)
|
|
408
|
-
.foregroundStyle(.secondary)
|
|
409
|
-
.lineLimit(1)
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
} compactLeading: {
|
|
413
|
-
// COMPACT LEADING: tiny icon identifying the activity
|
|
414
|
-
Image(systemName: context.state.driverPhoto)
|
|
415
|
-
.foregroundStyle(.green)
|
|
416
|
-
} compactTrailing: {
|
|
417
|
-
// COMPACT TRAILING: one key value
|
|
418
|
-
Text(timerInterval: context.state.eta, countsDown: true)
|
|
419
|
-
.frame(width: 44)
|
|
420
|
-
.monospacedDigit()
|
|
421
|
-
} minimal: {
|
|
422
|
-
// MINIMAL: shown when multiple activities compete
|
|
423
|
-
Image(systemName: "car.fill")
|
|
424
|
-
.foregroundStyle(.green)
|
|
425
|
-
}
|
|
426
|
-
.keylineTint(.green)
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
```
|
|
431
|
-
|
|
432
|
-
## Lock Screen Layout with Timer and Progress
|
|
433
|
-
|
|
434
|
-
```swift
|
|
435
|
-
struct RideLockScreenView: View {
|
|
436
|
-
let context: ActivityViewContext<RideAttributes>
|
|
437
|
-
|
|
438
|
-
var body: some View {
|
|
439
|
-
VStack(spacing: 12) {
|
|
440
|
-
// Header
|
|
441
|
-
HStack {
|
|
442
|
-
VStack(alignment: .leading) {
|
|
443
|
-
Text(context.state.status.displayName)
|
|
444
|
-
.font(.headline)
|
|
445
|
-
Text(context.state.vehicleDescription)
|
|
446
|
-
.font(.caption)
|
|
447
|
-
.foregroundStyle(.secondary)
|
|
448
|
-
}
|
|
449
|
-
Spacer()
|
|
450
|
-
// Live countdown timer (auto-updating, no code needed)
|
|
451
|
-
Text(timerInterval: context.state.eta, countsDown: true)
|
|
452
|
-
.font(.title2.monospacedDigit().bold())
|
|
453
|
-
.foregroundStyle(.green)
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
if context.isStale {
|
|
457
|
-
Label("Checking for updates...",
|
|
458
|
-
systemImage: "arrow.trianglehead.2.clockwise")
|
|
459
|
-
.font(.caption)
|
|
460
|
-
.foregroundStyle(.secondary)
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// Progress bar
|
|
464
|
-
ProgressView(value: context.state.status.progress, total: 1.0)
|
|
465
|
-
.tint(.green)
|
|
466
|
-
|
|
467
|
-
// Route
|
|
468
|
-
HStack {
|
|
469
|
-
VStack(alignment: .leading) {
|
|
470
|
-
Text("Pickup").font(.caption2).foregroundStyle(.secondary)
|
|
471
|
-
Text(context.attributes.pickupLocation).font(.caption).lineLimit(1)
|
|
472
|
-
}
|
|
473
|
-
Spacer()
|
|
474
|
-
Image(systemName: "arrow.right")
|
|
475
|
-
.font(.caption)
|
|
476
|
-
.foregroundStyle(.secondary)
|
|
477
|
-
Spacer()
|
|
478
|
-
VStack(alignment: .trailing) {
|
|
479
|
-
Text("Dropoff").font(.caption2).foregroundStyle(.secondary)
|
|
480
|
-
Text(context.attributes.dropoffLocation).font(.caption).lineLimit(1)
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
.padding()
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
```
|
|
488
|
-
|
|
489
|
-
## Multiple Concurrent Activities
|
|
490
|
-
|
|
491
|
-
An app can run multiple Live Activities simultaneously (system limit applies).
|
|
492
|
-
Track them by storing references or querying `Activity<T>.activities`.
|
|
493
|
-
|
|
494
|
-
```swift
|
|
495
|
-
@Observable
|
|
496
|
-
@MainActor
|
|
497
|
-
final class ActivityManager {
|
|
498
|
-
private(set) var activeDeliveries: [String: Activity<DeliveryAttributes>] = [:]
|
|
499
|
-
|
|
500
|
-
func startDelivery(orderID: String, attributes: DeliveryAttributes,
|
|
501
|
-
state: DeliveryAttributes.ContentState) async throws {
|
|
502
|
-
let content = ActivityContent(state: state, staleDate: nil, relevanceScore: 75)
|
|
503
|
-
let activity = try Activity.request(
|
|
504
|
-
attributes: attributes, content: content, pushType: .token
|
|
505
|
-
)
|
|
506
|
-
activeDeliveries[orderID] = activity
|
|
507
|
-
|
|
508
|
-
// Token forwarding
|
|
509
|
-
Task { [weak self] in
|
|
510
|
-
for await token in activity.pushTokenUpdates {
|
|
511
|
-
let tokenString = token.map { String(format: "%02x", $0) }.joined()
|
|
512
|
-
try? await ServerAPI.shared.registerActivityToken(tokenString, orderID: orderID)
|
|
513
|
-
}
|
|
514
|
-
self?.activeDeliveries.removeValue(forKey: orderID)
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
func updateDelivery(orderID: String, state: DeliveryAttributes.ContentState) async {
|
|
519
|
-
guard let activity = activeDeliveries[orderID] else { return }
|
|
520
|
-
let content = ActivityContent(state: state, staleDate: nil, relevanceScore: 80)
|
|
521
|
-
await activity.update(content)
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
func endDelivery(orderID: String, finalState: DeliveryAttributes.ContentState) async {
|
|
525
|
-
guard let activity = activeDeliveries[orderID] else { return }
|
|
526
|
-
let content = ActivityContent(state: finalState, staleDate: nil, relevanceScore: 0)
|
|
527
|
-
await activity.end(content, dismissalPolicy: .default)
|
|
528
|
-
activeDeliveries.removeValue(forKey: orderID)
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
/// Reconcile in-memory state with system activities on app launch
|
|
532
|
-
func reconcile() {
|
|
533
|
-
let systemActivities = Activity<DeliveryAttributes>.activities
|
|
534
|
-
for activity in systemActivities {
|
|
535
|
-
let orderID = "\(activity.attributes.orderNumber)"
|
|
536
|
-
if activeDeliveries[orderID] == nil {
|
|
537
|
-
activeDeliveries[orderID] = activity
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
## Observing Activity State Changes
|
|
545
|
-
|
|
546
|
-
```swift
|
|
547
|
-
func observeActivityState(_ activity: Activity<RideAttributes>) {
|
|
548
|
-
// State updates: .active, .pending, .stale, .ended, .dismissed
|
|
549
|
-
Task {
|
|
550
|
-
for await state in activity.activityStateUpdates {
|
|
551
|
-
switch state {
|
|
552
|
-
case .active:
|
|
553
|
-
print("Activity is visible and running")
|
|
554
|
-
case .pending:
|
|
555
|
-
// iOS 26+: scheduled but not yet displayed
|
|
556
|
-
print("Activity is pending start")
|
|
557
|
-
case .stale:
|
|
558
|
-
// The staleDate passed without an update
|
|
559
|
-
print("Content is stale -- update or end")
|
|
560
|
-
case .ended:
|
|
561
|
-
// Ended but may still be visible on Lock Screen
|
|
562
|
-
print("Activity ended, may still linger on Lock Screen")
|
|
563
|
-
case .dismissed:
|
|
564
|
-
// Fully removed from UI -- safe to release resources
|
|
565
|
-
print("Activity dismissed from Lock Screen")
|
|
566
|
-
cleanupResources(for: activity.id)
|
|
567
|
-
@unknown default:
|
|
568
|
-
break
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
// Content updates (observe state changes from push or other processes)
|
|
574
|
-
Task {
|
|
575
|
-
for await content in activity.contentUpdates {
|
|
576
|
-
print("New state: \(content.state)")
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
```
|
|
581
|
-
|
|
582
|
-
## Token Update Handling
|
|
583
|
-
|
|
584
|
-
Push tokens can change at any time. Always observe the async sequence and
|
|
585
|
-
re-register with your server.
|
|
586
|
-
|
|
587
|
-
```swift
|
|
588
|
-
func observePushToken(for activity: Activity<RideAttributes>) {
|
|
589
|
-
Task {
|
|
590
|
-
for await token in activity.pushTokenUpdates {
|
|
591
|
-
let tokenString = token.map { String(format: "%02x", $0) }.joined()
|
|
592
|
-
do {
|
|
593
|
-
try await ServerAPI.shared.registerActivityToken(
|
|
594
|
-
tokenString, activityID: activity.id
|
|
595
|
-
)
|
|
596
|
-
} catch {
|
|
597
|
-
// Retry with exponential backoff; token is critical for updates
|
|
598
|
-
print("Failed to register token: \(error)")
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
/// Observe the push-to-start token for remote activity creation (iOS 17.2+)
|
|
605
|
-
func observePushToStartToken() {
|
|
606
|
-
Task {
|
|
607
|
-
for await token in Activity<RideAttributes>.pushToStartTokenUpdates {
|
|
608
|
-
let tokenString = token.map { String(format: "%02x", $0) }.joined()
|
|
609
|
-
try? await ServerAPI.shared.registerPushToStartToken(tokenString)
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
```
|
|
614
|
-
|
|
615
|
-
## Authorization Check
|
|
616
|
-
|
|
617
|
-
Always check authorization before starting an activity. The user can disable
|
|
618
|
-
Live Activities in Settings at any time.
|
|
619
|
-
|
|
620
|
-
```swift
|
|
621
|
-
func checkLiveActivityAuthorization() async -> Bool {
|
|
622
|
-
let authInfo = ActivityAuthorizationInfo()
|
|
623
|
-
|
|
624
|
-
// Synchronous check
|
|
625
|
-
guard authInfo.areActivitiesEnabled else {
|
|
626
|
-
return false
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
return true
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
/// Observe authorization changes to react when user toggles the setting
|
|
633
|
-
func observeAuthorization() {
|
|
634
|
-
Task {
|
|
635
|
-
let authInfo = ActivityAuthorizationInfo()
|
|
636
|
-
for await enabled in authInfo.activityEnablementUpdates {
|
|
637
|
-
if enabled {
|
|
638
|
-
// Re-register push-to-start token
|
|
639
|
-
observePushToStartToken()
|
|
640
|
-
} else {
|
|
641
|
-
// Inform server to stop sending push updates
|
|
642
|
-
try? await ServerAPI.shared.disableActivityPush()
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
```
|
|
648
|
-
|
|
649
|
-
## Error Handling
|
|
650
|
-
|
|
651
|
-
```swift
|
|
652
|
-
func startActivitySafely(
|
|
653
|
-
attributes: DeliveryAttributes,
|
|
654
|
-
state: DeliveryAttributes.ContentState
|
|
655
|
-
) async {
|
|
656
|
-
let content = ActivityContent(state: state, staleDate: nil, relevanceScore: 75)
|
|
657
|
-
|
|
658
|
-
do {
|
|
659
|
-
let activity = try Activity.request(
|
|
660
|
-
attributes: attributes, content: content, pushType: .token
|
|
661
|
-
)
|
|
662
|
-
print("Started: \(activity.id)")
|
|
663
|
-
} catch let error as ActivityAuthorizationError {
|
|
664
|
-
switch error {
|
|
665
|
-
case .denied:
|
|
666
|
-
// User disabled Live Activities in Settings
|
|
667
|
-
print("Live Activities disabled by user")
|
|
668
|
-
case .globalMaximumExceeded:
|
|
669
|
-
// Too many Live Activities across all apps (system limit ~5)
|
|
670
|
-
print("System-wide activity limit reached")
|
|
671
|
-
case .targetMaximumExceeded:
|
|
672
|
-
// Too many Live Activities for this app
|
|
673
|
-
print("App activity limit reached -- end an existing one first")
|
|
674
|
-
default:
|
|
675
|
-
print("Authorization error: \(error)")
|
|
676
|
-
}
|
|
677
|
-
} catch {
|
|
678
|
-
print("Unexpected error: \(error)")
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
```
|
|
682
|
-
|
|
683
|
-
## Background Handling Considerations
|
|
684
|
-
|
|
685
|
-
Live Activities continue to display when the app is backgrounded or suspended.
|
|
686
|
-
Push-to-update is the primary mechanism for background updates. When the app
|
|
687
|
-
returns to foreground, reconcile local state with the activity's current
|
|
688
|
-
content.
|
|
689
|
-
|
|
690
|
-
```swift
|
|
691
|
-
@MainActor
|
|
692
|
-
func handleAppBecameActive() {
|
|
693
|
-
// Reconcile local state with live activities on foregrounding
|
|
694
|
-
let activities = Activity<DeliveryAttributes>.activities
|
|
695
|
-
for activity in activities {
|
|
696
|
-
switch activity.activityState {
|
|
697
|
-
case .active:
|
|
698
|
-
// Refresh from server in case pushes were missed
|
|
699
|
-
Task {
|
|
700
|
-
let serverState = try await ServerAPI.shared.fetchDeliveryState(
|
|
701
|
-
orderNumber: activity.attributes.orderNumber
|
|
702
|
-
)
|
|
703
|
-
let content = ActivityContent(
|
|
704
|
-
state: serverState,
|
|
705
|
-
staleDate: Date().addingTimeInterval(120),
|
|
706
|
-
relevanceScore: 80
|
|
707
|
-
)
|
|
708
|
-
await activity.update(content)
|
|
709
|
-
}
|
|
710
|
-
case .stale:
|
|
711
|
-
// Content is outdated -- update immediately
|
|
712
|
-
Task {
|
|
713
|
-
let serverState = try await ServerAPI.shared.fetchDeliveryState(
|
|
714
|
-
orderNumber: activity.attributes.orderNumber
|
|
715
|
-
)
|
|
716
|
-
let content = ActivityContent(
|
|
717
|
-
state: serverState,
|
|
718
|
-
staleDate: Date().addingTimeInterval(120),
|
|
719
|
-
relevanceScore: 80
|
|
720
|
-
)
|
|
721
|
-
await activity.update(content)
|
|
722
|
-
}
|
|
723
|
-
case .ended, .dismissed:
|
|
724
|
-
// Clean up local tracking
|
|
725
|
-
break
|
|
726
|
-
default:
|
|
727
|
-
break
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
```
|
|
732
|
-
|
|
733
|
-
For truly background-driven updates, rely on push-to-update rather than
|
|
734
|
-
Background App Refresh. Push updates are delivered reliably even when the app
|
|
735
|
-
is suspended, while Background App Refresh has limited and unpredictable
|
|
736
|
-
scheduling.
|
|
737
|
-
|
|
738
|
-
## Testing in Simulator and on Device
|
|
739
|
-
|
|
740
|
-
### Simulator
|
|
741
|
-
|
|
742
|
-
The Simulator supports Live Activity rendering on the Lock Screen and displays
|
|
743
|
-
the Dynamic Island on iPhone 14 Pro+ simulator models. Use Xcode previews for
|
|
744
|
-
rapid iteration:
|
|
745
|
-
|
|
746
|
-
```swift
|
|
747
|
-
#Preview("Lock Screen", as: .content, using: RideAttributes.preview) {
|
|
748
|
-
RideActivityWidget()
|
|
749
|
-
} contentStates: {
|
|
750
|
-
RideAttributes.ContentState(
|
|
751
|
-
driverName: "Alex",
|
|
752
|
-
driverPhoto: "car.fill",
|
|
753
|
-
vehicleDescription: "White Toyota Camry",
|
|
754
|
-
eta: Date()...Date().addingTimeInterval(300),
|
|
755
|
-
status: .driverEnRoute,
|
|
756
|
-
distanceRemaining: 1.5
|
|
757
|
-
)
|
|
758
|
-
RideAttributes.ContentState(
|
|
759
|
-
driverName: "Alex",
|
|
760
|
-
driverPhoto: "car.fill",
|
|
761
|
-
vehicleDescription: "White Toyota Camry",
|
|
762
|
-
eta: Date()...Date().addingTimeInterval(60),
|
|
763
|
-
status: .arriving,
|
|
764
|
-
distanceRemaining: 0.1
|
|
765
|
-
)
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
#Preview("Dynamic Island Compact", as: .dynamicIsland(.compact), using: RideAttributes.preview) {
|
|
769
|
-
RideActivityWidget()
|
|
770
|
-
} contentStates: {
|
|
771
|
-
RideAttributes.ContentState(
|
|
772
|
-
driverName: "Alex",
|
|
773
|
-
driverPhoto: "car.fill",
|
|
774
|
-
vehicleDescription: "White Toyota Camry",
|
|
775
|
-
eta: Date()...Date().addingTimeInterval(300),
|
|
776
|
-
status: .driverEnRoute,
|
|
777
|
-
distanceRemaining: 1.5
|
|
778
|
-
)
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
#Preview("Dynamic Island Expanded", as: .dynamicIsland(.expanded), using: RideAttributes.preview) {
|
|
782
|
-
RideActivityWidget()
|
|
783
|
-
} contentStates: {
|
|
784
|
-
RideAttributes.ContentState(
|
|
785
|
-
driverName: "Alex",
|
|
786
|
-
driverPhoto: "car.fill",
|
|
787
|
-
vehicleDescription: "White Toyota Camry",
|
|
788
|
-
eta: Date()...Date().addingTimeInterval(300),
|
|
789
|
-
status: .driverEnRoute,
|
|
790
|
-
distanceRemaining: 1.5
|
|
791
|
-
)
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
#Preview("Dynamic Island Minimal", as: .dynamicIsland(.minimal), using: RideAttributes.preview) {
|
|
795
|
-
RideActivityWidget()
|
|
796
|
-
} contentStates: {
|
|
797
|
-
RideAttributes.ContentState(
|
|
798
|
-
driverName: "Alex",
|
|
799
|
-
driverPhoto: "car.fill",
|
|
800
|
-
vehicleDescription: "White Toyota Camry",
|
|
801
|
-
eta: Date()...Date().addingTimeInterval(300),
|
|
802
|
-
status: .driverEnRoute,
|
|
803
|
-
distanceRemaining: 1.5
|
|
804
|
-
)
|
|
805
|
-
}
|
|
806
|
-
```
|
|
807
|
-
|
|
808
|
-
### Preview Data Helper
|
|
809
|
-
|
|
810
|
-
```swift
|
|
811
|
-
extension RideAttributes {
|
|
812
|
-
static var preview: RideAttributes {
|
|
813
|
-
RideAttributes(
|
|
814
|
-
riderName: "Jordan",
|
|
815
|
-
pickupLocation: "123 Main St",
|
|
816
|
-
dropoffLocation: "456 Oak Ave"
|
|
817
|
-
)
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
```
|
|
821
|
-
|
|
822
|
-
### On Device
|
|
823
|
-
|
|
824
|
-
Test push-to-update by sending payloads through APNs using a tool like `curl`
|
|
825
|
-
or a push notification testing app. The Simulator does not support APNs, so
|
|
826
|
-
push-to-update must be tested on a physical device.
|
|
827
|
-
|
|
828
|
-
```bash
|
|
829
|
-
# Example curl command for APNs push update (HTTP/2)
|
|
830
|
-
curl -v \
|
|
831
|
-
--http2 \
|
|
832
|
-
--header "apns-push-type: liveactivity" \
|
|
833
|
-
--header "apns-topic: com.example.app.push-type.liveactivity" \
|
|
834
|
-
--header "apns-priority: 10" \
|
|
835
|
-
--header "authorization: bearer $JWT_TOKEN" \
|
|
836
|
-
--data '{"aps":{"timestamp":1700000000,"event":"update","content-state":{"driverName":"Alex","driverPhoto":"car.fill","vehicleDescription":"White Toyota Camry","eta":{"lowerBound":1700000000,"upperBound":1700000300},"status":"driverArrived","distanceRemaining":0.0},"alert":{"title":"Driver Arrived","body":"Your driver is here!"}}}' \
|
|
837
|
-
https://api.push.apple.com/3/device/$DEVICE_PUSH_TOKEN
|
|
838
|
-
```
|
|
839
|
-
|
|
840
|
-
### Debugging Tips
|
|
841
|
-
|
|
842
|
-
- Check Console.app for `ActivityKit` log messages when activities fail to start.
|
|
843
|
-
- Verify `content-state` JSON keys match `ContentState` property names exactly
|
|
844
|
-
(including camelCase). Mismatches cause silent failures.
|
|
845
|
-
- Use `Activity<T>.activities` to inspect all running activities in the debugger.
|
|
846
|
-
- Set a breakpoint in `pushTokenUpdates` to verify tokens are being delivered.
|
|
847
|
-
- If activities do not appear, confirm `NSSupportsLiveActivities = YES` is in
|
|
848
|
-
the host app's Info.plist (not the widget extension's).
|
|
849
|
-
|
|
850
|
-
## Info.plist Keys Reference
|
|
851
|
-
|
|
852
|
-
| Key | Value | Purpose |
|
|
853
|
-
|---|---|---|
|
|
854
|
-
| `NSSupportsLiveActivities` | `YES` | Enable Live Activities (required) |
|
|
855
|
-
| `NSSupportsLiveActivitiesFrequentUpdates` | `YES` | Increase push update budget |
|
|
856
|
-
|
|
857
|
-
Both keys belong in the host app's Info.plist, not the widget extension.
|
|
858
|
-
|
|
859
|
-
## Apple Documentation Links
|
|
860
|
-
|
|
861
|
-
- [ActivityKit](https://sosumi.ai/documentation/activitykit)
|
|
862
|
-
- [ActivityAttributes](https://sosumi.ai/documentation/activitykit/activityattributes)
|
|
863
|
-
- [Activity](https://sosumi.ai/documentation/activitykit/activity)
|
|
864
|
-
- [ActivityContent](https://sosumi.ai/documentation/activitykit/activitycontent)
|
|
865
|
-
- [ActivityAuthorizationInfo](https://sosumi.ai/documentation/activitykit/activityauthorizationinfo)
|
|
866
|
-
- [DynamicIsland](https://sosumi.ai/documentation/widgetkit/dynamicisland)
|
|
867
|
-
- [ActivityConfiguration](https://sosumi.ai/documentation/widgetkit/activityconfiguration)
|
|
868
|
-
- [Starting and updating with push notifications](https://sosumi.ai/documentation/activitykit/starting-and-updating-live-activities-with-activitykit-push-notifications)
|
|
1
|
+
# Live Activity Patterns
|
|
2
|
+
|
|
3
|
+
Complete implementation patterns for ActivityKit Live Activities, Dynamic
|
|
4
|
+
Island, push-to-update, and lifecycle management. All patterns use modern
|
|
5
|
+
Swift async/await and target iOS 16.1+ unless noted.
|
|
6
|
+
|
|
7
|
+
## Contents
|
|
8
|
+
|
|
9
|
+
- [Complete ActivityAttributes and ContentState](#complete-activityattributes-and-contentstate)
|
|
10
|
+
- [Starting a Live Activity with All Parameters](#starting-a-live-activity-with-all-parameters)
|
|
11
|
+
- [Updating from the App](#updating-from-the-app)
|
|
12
|
+
- [Push-to-Update Server Payload Format](#push-to-update-server-payload-format)
|
|
13
|
+
- [Ending with Different Dismissal Policies](#ending-with-different-dismissal-policies)
|
|
14
|
+
- [Complete Dynamic Island Layout (All Regions)](#complete-dynamic-island-layout-all-regions)
|
|
15
|
+
- [Lock Screen Layout with Timer and Progress](#lock-screen-layout-with-timer-and-progress)
|
|
16
|
+
- [Multiple Concurrent Activities](#multiple-concurrent-activities)
|
|
17
|
+
- [Observing Activity State Changes](#observing-activity-state-changes)
|
|
18
|
+
- [Token Update Handling](#token-update-handling)
|
|
19
|
+
- [Authorization Check](#authorization-check)
|
|
20
|
+
- [Error Handling](#error-handling)
|
|
21
|
+
- [Background Handling Considerations](#background-handling-considerations)
|
|
22
|
+
- [Testing in Simulator and on Device](#testing-in-simulator-and-on-device)
|
|
23
|
+
- [Info.plist Keys Reference](#infoplist-keys-reference)
|
|
24
|
+
- [Apple Documentation Links](#apple-documentation-links)
|
|
25
|
+
|
|
26
|
+
## Complete ActivityAttributes and ContentState
|
|
27
|
+
|
|
28
|
+
Define the data model for your Live Activity. Static properties go on the
|
|
29
|
+
outer struct; dynamic properties go in `ContentState`.
|
|
30
|
+
|
|
31
|
+
```swift
|
|
32
|
+
import ActivityKit
|
|
33
|
+
|
|
34
|
+
struct RideAttributes: ActivityAttributes {
|
|
35
|
+
// Static -- set at creation, immutable for the activity lifetime
|
|
36
|
+
var riderName: String
|
|
37
|
+
var pickupLocation: String
|
|
38
|
+
var dropoffLocation: String
|
|
39
|
+
|
|
40
|
+
struct ContentState: Codable, Hashable {
|
|
41
|
+
var driverName: String
|
|
42
|
+
var driverPhoto: String // SF Symbol name or asset name
|
|
43
|
+
var vehicleDescription: String
|
|
44
|
+
var eta: ClosedRange<Date>
|
|
45
|
+
var status: RideStatus
|
|
46
|
+
var distanceRemaining: Double // miles
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
enum RideStatus: String, Codable, Hashable {
|
|
51
|
+
case driverAssigned
|
|
52
|
+
case driverEnRoute
|
|
53
|
+
case driverArrived
|
|
54
|
+
case inProgress
|
|
55
|
+
case arriving
|
|
56
|
+
case completed
|
|
57
|
+
case cancelled
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Keep `ContentState` lightweight. Every update serializes the entire struct, and
|
|
62
|
+
push payloads have a 4 KB size limit. Avoid storing images, large strings, or
|
|
63
|
+
deeply nested objects.
|
|
64
|
+
|
|
65
|
+
## Starting a Live Activity with All Parameters
|
|
66
|
+
|
|
67
|
+
```swift
|
|
68
|
+
import ActivityKit
|
|
69
|
+
|
|
70
|
+
@MainActor
|
|
71
|
+
func startRideActivity(
|
|
72
|
+
rider: String,
|
|
73
|
+
pickup: String,
|
|
74
|
+
dropoff: String,
|
|
75
|
+
driver: String,
|
|
76
|
+
vehicle: String
|
|
77
|
+
) async throws -> Activity<RideAttributes> {
|
|
78
|
+
// Check authorization before attempting to start
|
|
79
|
+
let authInfo = ActivityAuthorizationInfo()
|
|
80
|
+
guard authInfo.areActivitiesEnabled else {
|
|
81
|
+
throw RideError.liveActivitiesDisabled
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let attributes = RideAttributes(
|
|
85
|
+
riderName: rider,
|
|
86
|
+
pickupLocation: pickup,
|
|
87
|
+
dropoffLocation: dropoff
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
let initialState = RideAttributes.ContentState(
|
|
91
|
+
driverName: driver,
|
|
92
|
+
driverPhoto: "car.fill",
|
|
93
|
+
vehicleDescription: vehicle,
|
|
94
|
+
eta: Date()...Date().addingTimeInterval(600),
|
|
95
|
+
status: .driverAssigned,
|
|
96
|
+
distanceRemaining: 2.5
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
let content = ActivityContent(
|
|
100
|
+
state: initialState,
|
|
101
|
+
staleDate: Date().addingTimeInterval(120), // stale after 2 min
|
|
102
|
+
relevanceScore: 80
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
let activity = try Activity.request(
|
|
106
|
+
attributes: attributes,
|
|
107
|
+
content: content,
|
|
108
|
+
pushType: .token // Enable push updates
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
// Forward push token to server for remote updates
|
|
112
|
+
Task {
|
|
113
|
+
for await token in activity.pushTokenUpdates {
|
|
114
|
+
let tokenString = token.map { String(format: "%02x", $0) }.joined()
|
|
115
|
+
try? await ServerAPI.shared.registerActivityToken(
|
|
116
|
+
tokenString, rideID: activity.id
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Observe state changes for cleanup
|
|
122
|
+
Task {
|
|
123
|
+
for await state in activity.activityStateUpdates {
|
|
124
|
+
if state == .dismissed {
|
|
125
|
+
// Activity removed from UI -- clean up local resources
|
|
126
|
+
RideStore.shared.removeActivity(id: activity.id)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return activity
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Starting with Scheduled Date (iOS 26+)
|
|
136
|
+
|
|
137
|
+
Schedule the activity to appear at a future time without the app in foreground:
|
|
138
|
+
|
|
139
|
+
```swift
|
|
140
|
+
let gameTime = Calendar.current.date(
|
|
141
|
+
from: DateComponents(year: 2026, month: 3, day: 15, hour: 19, minute: 0)
|
|
142
|
+
)!
|
|
143
|
+
|
|
144
|
+
let activity = try Activity.request(
|
|
145
|
+
attributes: attributes,
|
|
146
|
+
content: content,
|
|
147
|
+
pushType: .token,
|
|
148
|
+
start: gameTime // iOS 26+
|
|
149
|
+
)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Starting with ActivityStyle (iOS 16.1+ type, `style:` parameter iOS 26+)
|
|
153
|
+
|
|
154
|
+
```swift
|
|
155
|
+
// Transient: system may auto-dismiss after a period
|
|
156
|
+
let activity = try Activity.request(
|
|
157
|
+
attributes: attributes,
|
|
158
|
+
content: content,
|
|
159
|
+
pushType: .token,
|
|
160
|
+
style: .transient // style: parameter requires iOS 26+
|
|
161
|
+
)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Updating from the App
|
|
165
|
+
|
|
166
|
+
```swift
|
|
167
|
+
func updateRideActivity(
|
|
168
|
+
_ activity: Activity<RideAttributes>,
|
|
169
|
+
newStatus: RideStatus,
|
|
170
|
+
eta: ClosedRange<Date>,
|
|
171
|
+
distance: Double,
|
|
172
|
+
showAlert: Bool = false
|
|
173
|
+
) async {
|
|
174
|
+
let updatedState = RideAttributes.ContentState(
|
|
175
|
+
driverName: activity.content.state.driverName,
|
|
176
|
+
driverPhoto: activity.content.state.driverPhoto,
|
|
177
|
+
vehicleDescription: activity.content.state.vehicleDescription,
|
|
178
|
+
eta: eta,
|
|
179
|
+
status: newStatus,
|
|
180
|
+
distanceRemaining: distance
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
let content = ActivityContent(
|
|
184
|
+
state: updatedState,
|
|
185
|
+
staleDate: Date().addingTimeInterval(120),
|
|
186
|
+
relevanceScore: newStatus == .driverArrived ? 100 : 80
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if showAlert {
|
|
190
|
+
await activity.update(content, alertConfiguration: AlertConfiguration(
|
|
191
|
+
title: "Ride Update",
|
|
192
|
+
body: alertMessage(for: newStatus),
|
|
193
|
+
sound: .default
|
|
194
|
+
))
|
|
195
|
+
} else {
|
|
196
|
+
await activity.update(content)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private func alertMessage(for status: RideStatus) -> String {
|
|
201
|
+
switch status {
|
|
202
|
+
case .driverArrived: "Your driver has arrived!"
|
|
203
|
+
case .arriving: "You're almost there!"
|
|
204
|
+
case .completed: "You've arrived at your destination."
|
|
205
|
+
default: "Your ride status has changed."
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Push-to-Update Server Payload Format
|
|
211
|
+
|
|
212
|
+
### Update Payload
|
|
213
|
+
|
|
214
|
+
```json
|
|
215
|
+
{
|
|
216
|
+
"aps": {
|
|
217
|
+
"timestamp": 1700000000,
|
|
218
|
+
"event": "update",
|
|
219
|
+
"content-state": {
|
|
220
|
+
"driverName": "Maria",
|
|
221
|
+
"driverPhoto": "car.fill",
|
|
222
|
+
"vehicleDescription": "White Toyota Camry",
|
|
223
|
+
"eta": {
|
|
224
|
+
"lowerBound": 1700000000,
|
|
225
|
+
"upperBound": 1700000300
|
|
226
|
+
},
|
|
227
|
+
"status": "driverArrived",
|
|
228
|
+
"distanceRemaining": 0.0
|
|
229
|
+
},
|
|
230
|
+
"stale-date": 1700000300,
|
|
231
|
+
"relevance-score": 100,
|
|
232
|
+
"alert": {
|
|
233
|
+
"title": "Ride Update",
|
|
234
|
+
"body": "Your driver has arrived!",
|
|
235
|
+
"sound": "default"
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### End Payload
|
|
242
|
+
|
|
243
|
+
```json
|
|
244
|
+
{
|
|
245
|
+
"aps": {
|
|
246
|
+
"timestamp": 1700002000,
|
|
247
|
+
"event": "end",
|
|
248
|
+
"dismissal-date": 1700005600,
|
|
249
|
+
"content-state": {
|
|
250
|
+
"driverName": "Maria",
|
|
251
|
+
"driverPhoto": "car.fill",
|
|
252
|
+
"vehicleDescription": "White Toyota Camry",
|
|
253
|
+
"eta": {
|
|
254
|
+
"lowerBound": 1700002000,
|
|
255
|
+
"upperBound": 1700002000
|
|
256
|
+
},
|
|
257
|
+
"status": "completed",
|
|
258
|
+
"distanceRemaining": 0.0
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Push-to-Start Payload (iOS 17.2+)
|
|
265
|
+
|
|
266
|
+
Send to the push-to-start token to remotely create an activity:
|
|
267
|
+
|
|
268
|
+
```json
|
|
269
|
+
{
|
|
270
|
+
"aps": {
|
|
271
|
+
"timestamp": 1700000000,
|
|
272
|
+
"event": "start",
|
|
273
|
+
"attributes-type": "RideAttributes",
|
|
274
|
+
"attributes": {
|
|
275
|
+
"riderName": "Jordan",
|
|
276
|
+
"pickupLocation": "123 Main St",
|
|
277
|
+
"dropoffLocation": "456 Oak Ave"
|
|
278
|
+
},
|
|
279
|
+
"content-state": {
|
|
280
|
+
"driverName": "Maria",
|
|
281
|
+
"driverPhoto": "car.fill",
|
|
282
|
+
"vehicleDescription": "White Toyota Camry",
|
|
283
|
+
"eta": {
|
|
284
|
+
"lowerBound": 1700000000,
|
|
285
|
+
"upperBound": 1700000600
|
|
286
|
+
},
|
|
287
|
+
"status": "driverAssigned",
|
|
288
|
+
"distanceRemaining": 3.2
|
|
289
|
+
},
|
|
290
|
+
"alert": {
|
|
291
|
+
"title": "Ride Matched",
|
|
292
|
+
"body": "Maria is on the way in a White Toyota Camry."
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Required APNs HTTP Headers
|
|
299
|
+
|
|
300
|
+
| Header | Value |
|
|
301
|
+
|---|---|
|
|
302
|
+
| `apns-push-type` | `liveactivity` |
|
|
303
|
+
| `apns-topic` | `<bundle-id>.push-type.liveactivity` |
|
|
304
|
+
| `apns-priority` | `5` (silent) or `10` (alert, updates with sound/banner) |
|
|
305
|
+
| `authorization` | `bearer <jwt>` (token auth) or use certificate auth |
|
|
306
|
+
|
|
307
|
+
The `content-state` JSON keys and value types must match the `ContentState`
|
|
308
|
+
Codable representation exactly. A type mismatch (e.g., sending a string where
|
|
309
|
+
a number is expected) causes the update to fail silently.
|
|
310
|
+
|
|
311
|
+
## Ending with Different Dismissal Policies
|
|
312
|
+
|
|
313
|
+
```swift
|
|
314
|
+
func endRideActivity(
|
|
315
|
+
_ activity: Activity<RideAttributes>,
|
|
316
|
+
finalStatus: RideStatus
|
|
317
|
+
) async {
|
|
318
|
+
let finalState = RideAttributes.ContentState(
|
|
319
|
+
driverName: activity.content.state.driverName,
|
|
320
|
+
driverPhoto: activity.content.state.driverPhoto,
|
|
321
|
+
vehicleDescription: activity.content.state.vehicleDescription,
|
|
322
|
+
eta: Date()...Date(),
|
|
323
|
+
status: finalStatus,
|
|
324
|
+
distanceRemaining: 0
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
let content = ActivityContent(state: finalState, staleDate: nil, relevanceScore: 0)
|
|
328
|
+
|
|
329
|
+
switch finalStatus {
|
|
330
|
+
case .completed:
|
|
331
|
+
// Keep on Lock Screen for 1 hour so user can review trip details
|
|
332
|
+
await activity.end(content, dismissalPolicy: .after(
|
|
333
|
+
Date().addingTimeInterval(3600)
|
|
334
|
+
))
|
|
335
|
+
case .cancelled:
|
|
336
|
+
// Remove immediately -- no useful info to show
|
|
337
|
+
await activity.end(content, dismissalPolicy: .immediate)
|
|
338
|
+
default:
|
|
339
|
+
// Let the system decide
|
|
340
|
+
await activity.end(content, dismissalPolicy: .default)
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Ending All Activities (cleanup on sign-out)
|
|
346
|
+
|
|
347
|
+
```swift
|
|
348
|
+
func endAllRideActivities() async {
|
|
349
|
+
for activity in Activity<RideAttributes>.activities {
|
|
350
|
+
await activity.end(nil, dismissalPolicy: .immediate)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
## Complete Dynamic Island Layout (All Regions)
|
|
356
|
+
|
|
357
|
+
```swift
|
|
358
|
+
struct RideActivityWidget: Widget {
|
|
359
|
+
var body: some WidgetConfiguration {
|
|
360
|
+
ActivityConfiguration(for: RideAttributes.self) { context in
|
|
361
|
+
// Lock Screen presentation
|
|
362
|
+
RideLockScreenView(context: context)
|
|
363
|
+
} dynamicIsland: { context in
|
|
364
|
+
DynamicIsland {
|
|
365
|
+
// EXPANDED: shown on long-press
|
|
366
|
+
DynamicIslandExpandedRegion(.leading) {
|
|
367
|
+
VStack(alignment: .leading, spacing: 4) {
|
|
368
|
+
Image(systemName: context.state.driverPhoto)
|
|
369
|
+
.font(.title2)
|
|
370
|
+
Text(context.state.driverName)
|
|
371
|
+
.font(.caption2)
|
|
372
|
+
.lineLimit(1)
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
DynamicIslandExpandedRegion(.trailing) {
|
|
377
|
+
VStack(alignment: .trailing, spacing: 4) {
|
|
378
|
+
Text(timerInterval: context.state.eta, countsDown: true)
|
|
379
|
+
.font(.title3.monospacedDigit())
|
|
380
|
+
Text(String(format: "%.1f mi", context.state.distanceRemaining))
|
|
381
|
+
.font(.caption2)
|
|
382
|
+
.foregroundStyle(.secondary)
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
DynamicIslandExpandedRegion(.center) {
|
|
387
|
+
Text(context.state.status.displayName)
|
|
388
|
+
.font(.headline)
|
|
389
|
+
.lineLimit(1)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
DynamicIslandExpandedRegion(.bottom) {
|
|
393
|
+
VStack(spacing: 8) {
|
|
394
|
+
ProgressView(
|
|
395
|
+
value: context.state.status.progress,
|
|
396
|
+
total: 1.0
|
|
397
|
+
)
|
|
398
|
+
.tint(.green)
|
|
399
|
+
|
|
400
|
+
HStack {
|
|
401
|
+
Label(context.attributes.pickupLocation,
|
|
402
|
+
systemImage: "mappin.circle.fill")
|
|
403
|
+
Spacer()
|
|
404
|
+
Label(context.attributes.dropoffLocation,
|
|
405
|
+
systemImage: "flag.checkered")
|
|
406
|
+
}
|
|
407
|
+
.font(.caption2)
|
|
408
|
+
.foregroundStyle(.secondary)
|
|
409
|
+
.lineLimit(1)
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
} compactLeading: {
|
|
413
|
+
// COMPACT LEADING: tiny icon identifying the activity
|
|
414
|
+
Image(systemName: context.state.driverPhoto)
|
|
415
|
+
.foregroundStyle(.green)
|
|
416
|
+
} compactTrailing: {
|
|
417
|
+
// COMPACT TRAILING: one key value
|
|
418
|
+
Text(timerInterval: context.state.eta, countsDown: true)
|
|
419
|
+
.frame(width: 44)
|
|
420
|
+
.monospacedDigit()
|
|
421
|
+
} minimal: {
|
|
422
|
+
// MINIMAL: shown when multiple activities compete
|
|
423
|
+
Image(systemName: "car.fill")
|
|
424
|
+
.foregroundStyle(.green)
|
|
425
|
+
}
|
|
426
|
+
.keylineTint(.green)
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
## Lock Screen Layout with Timer and Progress
|
|
433
|
+
|
|
434
|
+
```swift
|
|
435
|
+
struct RideLockScreenView: View {
|
|
436
|
+
let context: ActivityViewContext<RideAttributes>
|
|
437
|
+
|
|
438
|
+
var body: some View {
|
|
439
|
+
VStack(spacing: 12) {
|
|
440
|
+
// Header
|
|
441
|
+
HStack {
|
|
442
|
+
VStack(alignment: .leading) {
|
|
443
|
+
Text(context.state.status.displayName)
|
|
444
|
+
.font(.headline)
|
|
445
|
+
Text(context.state.vehicleDescription)
|
|
446
|
+
.font(.caption)
|
|
447
|
+
.foregroundStyle(.secondary)
|
|
448
|
+
}
|
|
449
|
+
Spacer()
|
|
450
|
+
// Live countdown timer (auto-updating, no code needed)
|
|
451
|
+
Text(timerInterval: context.state.eta, countsDown: true)
|
|
452
|
+
.font(.title2.monospacedDigit().bold())
|
|
453
|
+
.foregroundStyle(.green)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if context.isStale {
|
|
457
|
+
Label("Checking for updates...",
|
|
458
|
+
systemImage: "arrow.trianglehead.2.clockwise")
|
|
459
|
+
.font(.caption)
|
|
460
|
+
.foregroundStyle(.secondary)
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Progress bar
|
|
464
|
+
ProgressView(value: context.state.status.progress, total: 1.0)
|
|
465
|
+
.tint(.green)
|
|
466
|
+
|
|
467
|
+
// Route
|
|
468
|
+
HStack {
|
|
469
|
+
VStack(alignment: .leading) {
|
|
470
|
+
Text("Pickup").font(.caption2).foregroundStyle(.secondary)
|
|
471
|
+
Text(context.attributes.pickupLocation).font(.caption).lineLimit(1)
|
|
472
|
+
}
|
|
473
|
+
Spacer()
|
|
474
|
+
Image(systemName: "arrow.right")
|
|
475
|
+
.font(.caption)
|
|
476
|
+
.foregroundStyle(.secondary)
|
|
477
|
+
Spacer()
|
|
478
|
+
VStack(alignment: .trailing) {
|
|
479
|
+
Text("Dropoff").font(.caption2).foregroundStyle(.secondary)
|
|
480
|
+
Text(context.attributes.dropoffLocation).font(.caption).lineLimit(1)
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
.padding()
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
## Multiple Concurrent Activities
|
|
490
|
+
|
|
491
|
+
An app can run multiple Live Activities simultaneously (system limit applies).
|
|
492
|
+
Track them by storing references or querying `Activity<T>.activities`.
|
|
493
|
+
|
|
494
|
+
```swift
|
|
495
|
+
@Observable
|
|
496
|
+
@MainActor
|
|
497
|
+
final class ActivityManager {
|
|
498
|
+
private(set) var activeDeliveries: [String: Activity<DeliveryAttributes>] = [:]
|
|
499
|
+
|
|
500
|
+
func startDelivery(orderID: String, attributes: DeliveryAttributes,
|
|
501
|
+
state: DeliveryAttributes.ContentState) async throws {
|
|
502
|
+
let content = ActivityContent(state: state, staleDate: nil, relevanceScore: 75)
|
|
503
|
+
let activity = try Activity.request(
|
|
504
|
+
attributes: attributes, content: content, pushType: .token
|
|
505
|
+
)
|
|
506
|
+
activeDeliveries[orderID] = activity
|
|
507
|
+
|
|
508
|
+
// Token forwarding
|
|
509
|
+
Task { [weak self] in
|
|
510
|
+
for await token in activity.pushTokenUpdates {
|
|
511
|
+
let tokenString = token.map { String(format: "%02x", $0) }.joined()
|
|
512
|
+
try? await ServerAPI.shared.registerActivityToken(tokenString, orderID: orderID)
|
|
513
|
+
}
|
|
514
|
+
self?.activeDeliveries.removeValue(forKey: orderID)
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
func updateDelivery(orderID: String, state: DeliveryAttributes.ContentState) async {
|
|
519
|
+
guard let activity = activeDeliveries[orderID] else { return }
|
|
520
|
+
let content = ActivityContent(state: state, staleDate: nil, relevanceScore: 80)
|
|
521
|
+
await activity.update(content)
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
func endDelivery(orderID: String, finalState: DeliveryAttributes.ContentState) async {
|
|
525
|
+
guard let activity = activeDeliveries[orderID] else { return }
|
|
526
|
+
let content = ActivityContent(state: finalState, staleDate: nil, relevanceScore: 0)
|
|
527
|
+
await activity.end(content, dismissalPolicy: .default)
|
|
528
|
+
activeDeliveries.removeValue(forKey: orderID)
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/// Reconcile in-memory state with system activities on app launch
|
|
532
|
+
func reconcile() {
|
|
533
|
+
let systemActivities = Activity<DeliveryAttributes>.activities
|
|
534
|
+
for activity in systemActivities {
|
|
535
|
+
let orderID = "\(activity.attributes.orderNumber)"
|
|
536
|
+
if activeDeliveries[orderID] == nil {
|
|
537
|
+
activeDeliveries[orderID] = activity
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
## Observing Activity State Changes
|
|
545
|
+
|
|
546
|
+
```swift
|
|
547
|
+
func observeActivityState(_ activity: Activity<RideAttributes>) {
|
|
548
|
+
// State updates: .active, .pending, .stale, .ended, .dismissed
|
|
549
|
+
Task {
|
|
550
|
+
for await state in activity.activityStateUpdates {
|
|
551
|
+
switch state {
|
|
552
|
+
case .active:
|
|
553
|
+
print("Activity is visible and running")
|
|
554
|
+
case .pending:
|
|
555
|
+
// iOS 26+: scheduled but not yet displayed
|
|
556
|
+
print("Activity is pending start")
|
|
557
|
+
case .stale:
|
|
558
|
+
// The staleDate passed without an update
|
|
559
|
+
print("Content is stale -- update or end")
|
|
560
|
+
case .ended:
|
|
561
|
+
// Ended but may still be visible on Lock Screen
|
|
562
|
+
print("Activity ended, may still linger on Lock Screen")
|
|
563
|
+
case .dismissed:
|
|
564
|
+
// Fully removed from UI -- safe to release resources
|
|
565
|
+
print("Activity dismissed from Lock Screen")
|
|
566
|
+
cleanupResources(for: activity.id)
|
|
567
|
+
@unknown default:
|
|
568
|
+
break
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Content updates (observe state changes from push or other processes)
|
|
574
|
+
Task {
|
|
575
|
+
for await content in activity.contentUpdates {
|
|
576
|
+
print("New state: \(content.state)")
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
## Token Update Handling
|
|
583
|
+
|
|
584
|
+
Push tokens can change at any time. Always observe the async sequence and
|
|
585
|
+
re-register with your server.
|
|
586
|
+
|
|
587
|
+
```swift
|
|
588
|
+
func observePushToken(for activity: Activity<RideAttributes>) {
|
|
589
|
+
Task {
|
|
590
|
+
for await token in activity.pushTokenUpdates {
|
|
591
|
+
let tokenString = token.map { String(format: "%02x", $0) }.joined()
|
|
592
|
+
do {
|
|
593
|
+
try await ServerAPI.shared.registerActivityToken(
|
|
594
|
+
tokenString, activityID: activity.id
|
|
595
|
+
)
|
|
596
|
+
} catch {
|
|
597
|
+
// Retry with exponential backoff; token is critical for updates
|
|
598
|
+
print("Failed to register token: \(error)")
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/// Observe the push-to-start token for remote activity creation (iOS 17.2+)
|
|
605
|
+
func observePushToStartToken() {
|
|
606
|
+
Task {
|
|
607
|
+
for await token in Activity<RideAttributes>.pushToStartTokenUpdates {
|
|
608
|
+
let tokenString = token.map { String(format: "%02x", $0) }.joined()
|
|
609
|
+
try? await ServerAPI.shared.registerPushToStartToken(tokenString)
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
## Authorization Check
|
|
616
|
+
|
|
617
|
+
Always check authorization before starting an activity. The user can disable
|
|
618
|
+
Live Activities in Settings at any time.
|
|
619
|
+
|
|
620
|
+
```swift
|
|
621
|
+
func checkLiveActivityAuthorization() async -> Bool {
|
|
622
|
+
let authInfo = ActivityAuthorizationInfo()
|
|
623
|
+
|
|
624
|
+
// Synchronous check
|
|
625
|
+
guard authInfo.areActivitiesEnabled else {
|
|
626
|
+
return false
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
return true
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/// Observe authorization changes to react when user toggles the setting
|
|
633
|
+
func observeAuthorization() {
|
|
634
|
+
Task {
|
|
635
|
+
let authInfo = ActivityAuthorizationInfo()
|
|
636
|
+
for await enabled in authInfo.activityEnablementUpdates {
|
|
637
|
+
if enabled {
|
|
638
|
+
// Re-register push-to-start token
|
|
639
|
+
observePushToStartToken()
|
|
640
|
+
} else {
|
|
641
|
+
// Inform server to stop sending push updates
|
|
642
|
+
try? await ServerAPI.shared.disableActivityPush()
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
## Error Handling
|
|
650
|
+
|
|
651
|
+
```swift
|
|
652
|
+
func startActivitySafely(
|
|
653
|
+
attributes: DeliveryAttributes,
|
|
654
|
+
state: DeliveryAttributes.ContentState
|
|
655
|
+
) async {
|
|
656
|
+
let content = ActivityContent(state: state, staleDate: nil, relevanceScore: 75)
|
|
657
|
+
|
|
658
|
+
do {
|
|
659
|
+
let activity = try Activity.request(
|
|
660
|
+
attributes: attributes, content: content, pushType: .token
|
|
661
|
+
)
|
|
662
|
+
print("Started: \(activity.id)")
|
|
663
|
+
} catch let error as ActivityAuthorizationError {
|
|
664
|
+
switch error {
|
|
665
|
+
case .denied:
|
|
666
|
+
// User disabled Live Activities in Settings
|
|
667
|
+
print("Live Activities disabled by user")
|
|
668
|
+
case .globalMaximumExceeded:
|
|
669
|
+
// Too many Live Activities across all apps (system limit ~5)
|
|
670
|
+
print("System-wide activity limit reached")
|
|
671
|
+
case .targetMaximumExceeded:
|
|
672
|
+
// Too many Live Activities for this app
|
|
673
|
+
print("App activity limit reached -- end an existing one first")
|
|
674
|
+
default:
|
|
675
|
+
print("Authorization error: \(error)")
|
|
676
|
+
}
|
|
677
|
+
} catch {
|
|
678
|
+
print("Unexpected error: \(error)")
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
## Background Handling Considerations
|
|
684
|
+
|
|
685
|
+
Live Activities continue to display when the app is backgrounded or suspended.
|
|
686
|
+
Push-to-update is the primary mechanism for background updates. When the app
|
|
687
|
+
returns to foreground, reconcile local state with the activity's current
|
|
688
|
+
content.
|
|
689
|
+
|
|
690
|
+
```swift
|
|
691
|
+
@MainActor
|
|
692
|
+
func handleAppBecameActive() {
|
|
693
|
+
// Reconcile local state with live activities on foregrounding
|
|
694
|
+
let activities = Activity<DeliveryAttributes>.activities
|
|
695
|
+
for activity in activities {
|
|
696
|
+
switch activity.activityState {
|
|
697
|
+
case .active:
|
|
698
|
+
// Refresh from server in case pushes were missed
|
|
699
|
+
Task {
|
|
700
|
+
let serverState = try await ServerAPI.shared.fetchDeliveryState(
|
|
701
|
+
orderNumber: activity.attributes.orderNumber
|
|
702
|
+
)
|
|
703
|
+
let content = ActivityContent(
|
|
704
|
+
state: serverState,
|
|
705
|
+
staleDate: Date().addingTimeInterval(120),
|
|
706
|
+
relevanceScore: 80
|
|
707
|
+
)
|
|
708
|
+
await activity.update(content)
|
|
709
|
+
}
|
|
710
|
+
case .stale:
|
|
711
|
+
// Content is outdated -- update immediately
|
|
712
|
+
Task {
|
|
713
|
+
let serverState = try await ServerAPI.shared.fetchDeliveryState(
|
|
714
|
+
orderNumber: activity.attributes.orderNumber
|
|
715
|
+
)
|
|
716
|
+
let content = ActivityContent(
|
|
717
|
+
state: serverState,
|
|
718
|
+
staleDate: Date().addingTimeInterval(120),
|
|
719
|
+
relevanceScore: 80
|
|
720
|
+
)
|
|
721
|
+
await activity.update(content)
|
|
722
|
+
}
|
|
723
|
+
case .ended, .dismissed:
|
|
724
|
+
// Clean up local tracking
|
|
725
|
+
break
|
|
726
|
+
default:
|
|
727
|
+
break
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
For truly background-driven updates, rely on push-to-update rather than
|
|
734
|
+
Background App Refresh. Push updates are delivered reliably even when the app
|
|
735
|
+
is suspended, while Background App Refresh has limited and unpredictable
|
|
736
|
+
scheduling.
|
|
737
|
+
|
|
738
|
+
## Testing in Simulator and on Device
|
|
739
|
+
|
|
740
|
+
### Simulator
|
|
741
|
+
|
|
742
|
+
The Simulator supports Live Activity rendering on the Lock Screen and displays
|
|
743
|
+
the Dynamic Island on iPhone 14 Pro+ simulator models. Use Xcode previews for
|
|
744
|
+
rapid iteration:
|
|
745
|
+
|
|
746
|
+
```swift
|
|
747
|
+
#Preview("Lock Screen", as: .content, using: RideAttributes.preview) {
|
|
748
|
+
RideActivityWidget()
|
|
749
|
+
} contentStates: {
|
|
750
|
+
RideAttributes.ContentState(
|
|
751
|
+
driverName: "Alex",
|
|
752
|
+
driverPhoto: "car.fill",
|
|
753
|
+
vehicleDescription: "White Toyota Camry",
|
|
754
|
+
eta: Date()...Date().addingTimeInterval(300),
|
|
755
|
+
status: .driverEnRoute,
|
|
756
|
+
distanceRemaining: 1.5
|
|
757
|
+
)
|
|
758
|
+
RideAttributes.ContentState(
|
|
759
|
+
driverName: "Alex",
|
|
760
|
+
driverPhoto: "car.fill",
|
|
761
|
+
vehicleDescription: "White Toyota Camry",
|
|
762
|
+
eta: Date()...Date().addingTimeInterval(60),
|
|
763
|
+
status: .arriving,
|
|
764
|
+
distanceRemaining: 0.1
|
|
765
|
+
)
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
#Preview("Dynamic Island Compact", as: .dynamicIsland(.compact), using: RideAttributes.preview) {
|
|
769
|
+
RideActivityWidget()
|
|
770
|
+
} contentStates: {
|
|
771
|
+
RideAttributes.ContentState(
|
|
772
|
+
driverName: "Alex",
|
|
773
|
+
driverPhoto: "car.fill",
|
|
774
|
+
vehicleDescription: "White Toyota Camry",
|
|
775
|
+
eta: Date()...Date().addingTimeInterval(300),
|
|
776
|
+
status: .driverEnRoute,
|
|
777
|
+
distanceRemaining: 1.5
|
|
778
|
+
)
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
#Preview("Dynamic Island Expanded", as: .dynamicIsland(.expanded), using: RideAttributes.preview) {
|
|
782
|
+
RideActivityWidget()
|
|
783
|
+
} contentStates: {
|
|
784
|
+
RideAttributes.ContentState(
|
|
785
|
+
driverName: "Alex",
|
|
786
|
+
driverPhoto: "car.fill",
|
|
787
|
+
vehicleDescription: "White Toyota Camry",
|
|
788
|
+
eta: Date()...Date().addingTimeInterval(300),
|
|
789
|
+
status: .driverEnRoute,
|
|
790
|
+
distanceRemaining: 1.5
|
|
791
|
+
)
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
#Preview("Dynamic Island Minimal", as: .dynamicIsland(.minimal), using: RideAttributes.preview) {
|
|
795
|
+
RideActivityWidget()
|
|
796
|
+
} contentStates: {
|
|
797
|
+
RideAttributes.ContentState(
|
|
798
|
+
driverName: "Alex",
|
|
799
|
+
driverPhoto: "car.fill",
|
|
800
|
+
vehicleDescription: "White Toyota Camry",
|
|
801
|
+
eta: Date()...Date().addingTimeInterval(300),
|
|
802
|
+
status: .driverEnRoute,
|
|
803
|
+
distanceRemaining: 1.5
|
|
804
|
+
)
|
|
805
|
+
}
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
### Preview Data Helper
|
|
809
|
+
|
|
810
|
+
```swift
|
|
811
|
+
extension RideAttributes {
|
|
812
|
+
static var preview: RideAttributes {
|
|
813
|
+
RideAttributes(
|
|
814
|
+
riderName: "Jordan",
|
|
815
|
+
pickupLocation: "123 Main St",
|
|
816
|
+
dropoffLocation: "456 Oak Ave"
|
|
817
|
+
)
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
### On Device
|
|
823
|
+
|
|
824
|
+
Test push-to-update by sending payloads through APNs using a tool like `curl`
|
|
825
|
+
or a push notification testing app. The Simulator does not support APNs, so
|
|
826
|
+
push-to-update must be tested on a physical device.
|
|
827
|
+
|
|
828
|
+
```bash
|
|
829
|
+
# Example curl command for APNs push update (HTTP/2)
|
|
830
|
+
curl -v \
|
|
831
|
+
--http2 \
|
|
832
|
+
--header "apns-push-type: liveactivity" \
|
|
833
|
+
--header "apns-topic: com.example.app.push-type.liveactivity" \
|
|
834
|
+
--header "apns-priority: 10" \
|
|
835
|
+
--header "authorization: bearer $JWT_TOKEN" \
|
|
836
|
+
--data '{"aps":{"timestamp":1700000000,"event":"update","content-state":{"driverName":"Alex","driverPhoto":"car.fill","vehicleDescription":"White Toyota Camry","eta":{"lowerBound":1700000000,"upperBound":1700000300},"status":"driverArrived","distanceRemaining":0.0},"alert":{"title":"Driver Arrived","body":"Your driver is here!"}}}' \
|
|
837
|
+
https://api.push.apple.com/3/device/$DEVICE_PUSH_TOKEN
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
### Debugging Tips
|
|
841
|
+
|
|
842
|
+
- Check Console.app for `ActivityKit` log messages when activities fail to start.
|
|
843
|
+
- Verify `content-state` JSON keys match `ContentState` property names exactly
|
|
844
|
+
(including camelCase). Mismatches cause silent failures.
|
|
845
|
+
- Use `Activity<T>.activities` to inspect all running activities in the debugger.
|
|
846
|
+
- Set a breakpoint in `pushTokenUpdates` to verify tokens are being delivered.
|
|
847
|
+
- If activities do not appear, confirm `NSSupportsLiveActivities = YES` is in
|
|
848
|
+
the host app's Info.plist (not the widget extension's).
|
|
849
|
+
|
|
850
|
+
## Info.plist Keys Reference
|
|
851
|
+
|
|
852
|
+
| Key | Value | Purpose |
|
|
853
|
+
|---|---|---|
|
|
854
|
+
| `NSSupportsLiveActivities` | `YES` | Enable Live Activities (required) |
|
|
855
|
+
| `NSSupportsLiveActivitiesFrequentUpdates` | `YES` | Increase push update budget |
|
|
856
|
+
|
|
857
|
+
Both keys belong in the host app's Info.plist, not the widget extension.
|
|
858
|
+
|
|
859
|
+
## Apple Documentation Links
|
|
860
|
+
|
|
861
|
+
- [ActivityKit](https://sosumi.ai/documentation/activitykit)
|
|
862
|
+
- [ActivityAttributes](https://sosumi.ai/documentation/activitykit/activityattributes)
|
|
863
|
+
- [Activity](https://sosumi.ai/documentation/activitykit/activity)
|
|
864
|
+
- [ActivityContent](https://sosumi.ai/documentation/activitykit/activitycontent)
|
|
865
|
+
- [ActivityAuthorizationInfo](https://sosumi.ai/documentation/activitykit/activityauthorizationinfo)
|
|
866
|
+
- [DynamicIsland](https://sosumi.ai/documentation/widgetkit/dynamicisland)
|
|
867
|
+
- [ActivityConfiguration](https://sosumi.ai/documentation/widgetkit/activityconfiguration)
|
|
868
|
+
- [Starting and updating with push notifications](https://sosumi.ai/documentation/activitykit/starting-and-updating-live-activities-with-activitykit-push-notifications)
|