@devo-bmad-custom/agent-orchestration 1.0.2 → 1.0.4
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 +33 -0
- 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/master-orchestrator.md +15 -0
- package/src/_memory/config.yaml +11 -11
- package/src/_memory/master-orchestrator-sidecar/instructions.md +97 -47
- 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/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/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,782 +1,782 @@
|
|
|
1
|
-
# TipKit Patterns Reference
|
|
2
|
-
|
|
3
|
-
Complete implementation patterns for TipKit including custom styles, event-based
|
|
4
|
-
rules, tip groups, testing strategies, onboarding flows, and SwiftUI previews.
|
|
5
|
-
All examples target iOS 17+ with Swift 6.2 conventions.
|
|
6
|
-
|
|
7
|
-
## Contents
|
|
8
|
-
|
|
9
|
-
- [Complete Tip with Rules and Events](#complete-tip-with-rules-and-events)
|
|
10
|
-
- [TipView and popoverTip Placement](#tipview-and-popovertip-placement)
|
|
11
|
-
- [Event-Based Rule with Donation Counting](#event-based-rule-with-donation-counting)
|
|
12
|
-
- [Custom TipViewStyle](#custom-tipviewstyle)
|
|
13
|
-
- [TipGroup Sequencing](#tipgroup-sequencing)
|
|
14
|
-
- [Testing Strategies](#testing-strategies)
|
|
15
|
-
- [Tip with Action Buttons](#tip-with-action-buttons)
|
|
16
|
-
- [Integration with Onboarding Flow](#integration-with-onboarding-flow)
|
|
17
|
-
- [Full App Integration Example](#full-app-integration-example)
|
|
18
|
-
|
|
19
|
-
## Complete Tip with Rules and Events
|
|
20
|
-
|
|
21
|
-
A full-featured tip combining parameter-based and event-based rules. The tip
|
|
22
|
-
appears only after the user has logged in and opened the app at least three
|
|
23
|
-
times, ensuring they are familiar with the basics before seeing advanced
|
|
24
|
-
feature discovery.
|
|
25
|
-
|
|
26
|
-
```swift
|
|
27
|
-
import TipKit
|
|
28
|
-
|
|
29
|
-
struct AdvancedSearchTip: Tip {
|
|
30
|
-
// Parameter rule: user must be logged in
|
|
31
|
-
@Parameter
|
|
32
|
-
static var isLoggedIn: Bool = false
|
|
33
|
-
|
|
34
|
-
// Event rule: user must have performed searches
|
|
35
|
-
static let searchPerformed = Tips.Event(id: "searchPerformed")
|
|
36
|
-
|
|
37
|
-
var title: Text {
|
|
38
|
-
Text("Try Advanced Search")
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
var message: Text? {
|
|
42
|
-
Text("Filter results by date, category, and location for faster discovery.")
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
var image: Image? {
|
|
46
|
-
Image(systemName: "magnifyingglass")
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// All rules must pass before the tip becomes eligible
|
|
50
|
-
var rules: [Rule] {
|
|
51
|
-
#Rule(Self.$isLoggedIn) { $0 == true }
|
|
52
|
-
#Rule(Self.searchPerformed) { $0.donations.count >= 3 }
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
var options: [TipOption] {
|
|
56
|
-
MaxDisplayCount(5)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### Donating to Events
|
|
62
|
-
|
|
63
|
-
Place event donations at the point where the user action occurs. Each
|
|
64
|
-
donation increments the internal counter that rules evaluate against.
|
|
65
|
-
|
|
66
|
-
```swift
|
|
67
|
-
struct SearchView: View {
|
|
68
|
-
@State private var query = ""
|
|
69
|
-
|
|
70
|
-
var body: some View {
|
|
71
|
-
SearchBar(text: $query, onSubmit: {
|
|
72
|
-
performSearch(query)
|
|
73
|
-
// Donate each time the user searches
|
|
74
|
-
AdvancedSearchTip.searchPerformed.donate()
|
|
75
|
-
})
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### Setting Parameters
|
|
81
|
-
|
|
82
|
-
Set parameter values when the relevant app state changes. Parameters persist
|
|
83
|
-
across launches via the TipKit datastore.
|
|
84
|
-
|
|
85
|
-
```swift
|
|
86
|
-
func handleLoginSuccess() {
|
|
87
|
-
AdvancedSearchTip.isLoggedIn = true
|
|
88
|
-
}
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
## TipView and popoverTip Placement
|
|
92
|
-
|
|
93
|
-
### Inline TipView in a List
|
|
94
|
-
|
|
95
|
-
Place a `TipView` as a list row for contextual inline discovery. The tip
|
|
96
|
-
appears as part of the list content and animates away when dismissed or
|
|
97
|
-
invalidated.
|
|
98
|
-
|
|
99
|
-
```swift
|
|
100
|
-
struct ItemListView: View {
|
|
101
|
-
let filterTip = FilterTip()
|
|
102
|
-
@State private var items: [Item] = []
|
|
103
|
-
|
|
104
|
-
var body: some View {
|
|
105
|
-
List {
|
|
106
|
-
TipView(filterTip)
|
|
107
|
-
|
|
108
|
-
ForEach(items) { item in
|
|
109
|
-
ItemRow(item: item)
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
.navigationTitle("Items")
|
|
113
|
-
.toolbar {
|
|
114
|
-
ToolbarItem(placement: .primaryAction) {
|
|
115
|
-
Button {
|
|
116
|
-
showFilters()
|
|
117
|
-
filterTip.invalidate(reason: .actionPerformed)
|
|
118
|
-
} label: {
|
|
119
|
-
Image(systemName: "line.3.horizontal.decrease.circle")
|
|
120
|
-
}
|
|
121
|
-
.popoverTip(filterTip, arrowEdge: .top)
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
### Popover on Navigation Bar Button
|
|
129
|
-
|
|
130
|
-
Attach a popover tip to a toolbar button. The popover arrow points to the
|
|
131
|
-
button, drawing the user's attention to the exact control.
|
|
132
|
-
|
|
133
|
-
```swift
|
|
134
|
-
struct EditorView: View {
|
|
135
|
-
let undoTip = UndoShortcutTip()
|
|
136
|
-
|
|
137
|
-
var body: some View {
|
|
138
|
-
TextEditor(text: $text)
|
|
139
|
-
.toolbar {
|
|
140
|
-
ToolbarItem(placement: .primaryAction) {
|
|
141
|
-
Button("Undo", systemImage: "arrow.uturn.backward") {
|
|
142
|
-
undoLastAction()
|
|
143
|
-
undoTip.invalidate(reason: .actionPerformed)
|
|
144
|
-
}
|
|
145
|
-
.popoverTip(undoTip, arrowEdge: .top)
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### Popover on Tab Bar Item
|
|
153
|
-
|
|
154
|
-
Use `popoverTip` on a `Tab` label view inside a `TabView` to highlight a
|
|
155
|
-
new tab.
|
|
156
|
-
|
|
157
|
-
```swift
|
|
158
|
-
struct MainTabView: View {
|
|
159
|
-
let newTabTip = NewFeatureTabTip()
|
|
160
|
-
|
|
161
|
-
var body: some View {
|
|
162
|
-
TabView {
|
|
163
|
-
Tab("Home", systemImage: "house") {
|
|
164
|
-
HomeView()
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
Tab("Discover", systemImage: "sparkles") {
|
|
168
|
-
DiscoverView()
|
|
169
|
-
}
|
|
170
|
-
.popoverTip(newTabTip)
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
## Event-Based Rule with Donation Counting
|
|
177
|
-
|
|
178
|
-
Track how many times the user performs an action, then show a tip suggesting
|
|
179
|
-
a more efficient alternative. This pattern is effective for progressive
|
|
180
|
-
disclosure: let users learn the basic workflow first, then reveal shortcuts.
|
|
181
|
-
|
|
182
|
-
```swift
|
|
183
|
-
struct KeyboardShortcutTip: Tip {
|
|
184
|
-
static let manualSaveEvent = Tips.Event(id: "manualSave")
|
|
185
|
-
|
|
186
|
-
var title: Text {
|
|
187
|
-
Text("Save Faster with Command-S")
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
var message: Text? {
|
|
191
|
-
Text("Press Command-S instead of using the menu to save your work instantly.")
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
var image: Image? {
|
|
195
|
-
Image(systemName: "keyboard")
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
var rules: [Rule] {
|
|
199
|
-
// Show after user has manually saved 5 times via button
|
|
200
|
-
#Rule(Self.manualSaveEvent) { $0.donations.count >= 5 }
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
struct DocumentView: View {
|
|
205
|
-
let shortcutTip = KeyboardShortcutTip()
|
|
206
|
-
|
|
207
|
-
var body: some View {
|
|
208
|
-
VStack {
|
|
209
|
-
TipView(shortcutTip)
|
|
210
|
-
DocumentEditor(document: $document)
|
|
211
|
-
}
|
|
212
|
-
.toolbar {
|
|
213
|
-
ToolbarItem {
|
|
214
|
-
Button("Save") {
|
|
215
|
-
saveDocument()
|
|
216
|
-
KeyboardShortcutTip.manualSaveEvent.donate()
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
### Event Donations with Associated Values
|
|
225
|
-
|
|
226
|
-
Attach a `DonationValue` to event donations for richer rule evaluation.
|
|
227
|
-
Use `Codable`-conforming types to provide context about each donation.
|
|
228
|
-
|
|
229
|
-
```swift
|
|
230
|
-
struct DetailedTip: Tip {
|
|
231
|
-
struct DonationInfo: Codable, Sendable {
|
|
232
|
-
let category: String
|
|
233
|
-
let timestamp: Date
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
static let itemViewed = Tips.Event<DonationInfo>(id: "itemViewed")
|
|
237
|
-
|
|
238
|
-
var rules: [Rule] {
|
|
239
|
-
#Rule(Self.itemViewed) {
|
|
240
|
-
$0.donations.filter {
|
|
241
|
-
$0.category == "premium"
|
|
242
|
-
}.count >= 3
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
var title: Text { Text("Unlock Premium Content") }
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Donate with associated value
|
|
250
|
-
DetailedTip.itemViewed.donate(
|
|
251
|
-
DetailedTip.DonationInfo(category: "premium", timestamp: .now)
|
|
252
|
-
)
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
## Custom TipViewStyle
|
|
256
|
-
|
|
257
|
-
Create a branded tip appearance that matches the app's design language.
|
|
258
|
-
The `Configuration` provides access to the tip's title, message, image,
|
|
259
|
-
and actions.
|
|
260
|
-
|
|
261
|
-
```swift
|
|
262
|
-
struct BrandedTipStyle: TipViewStyle {
|
|
263
|
-
func makeBody(configuration: Configuration) -> some View {
|
|
264
|
-
HStack(alignment: .top, spacing: 16) {
|
|
265
|
-
configuration.image?
|
|
266
|
-
.font(.system(size: 24))
|
|
267
|
-
.foregroundStyle(.white)
|
|
268
|
-
.frame(width: 44, height: 44)
|
|
269
|
-
.background(.blue.gradient, in: RoundedRectangle(cornerRadius: 10))
|
|
270
|
-
|
|
271
|
-
VStack(alignment: .leading, spacing: 6) {
|
|
272
|
-
configuration.title
|
|
273
|
-
.font(.headline)
|
|
274
|
-
|
|
275
|
-
configuration.message?
|
|
276
|
-
.font(.subheadline)
|
|
277
|
-
.foregroundStyle(.secondary)
|
|
278
|
-
|
|
279
|
-
if !configuration.actions.isEmpty {
|
|
280
|
-
HStack(spacing: 12) {
|
|
281
|
-
ForEach(configuration.actions) { action in
|
|
282
|
-
Button(action: action.handler) {
|
|
283
|
-
action.label
|
|
284
|
-
.font(.subheadline.bold())
|
|
285
|
-
}
|
|
286
|
-
.buttonStyle(.bordered)
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
.padding(.top, 4)
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
.padding()
|
|
294
|
-
.background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16))
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
### Applying the Custom Style
|
|
300
|
-
|
|
301
|
-
Apply the style to individual `TipView` instances or set it as the
|
|
302
|
-
environment default.
|
|
303
|
-
|
|
304
|
-
```swift
|
|
305
|
-
// Per view
|
|
306
|
-
TipView(myTip)
|
|
307
|
-
.tipViewStyle(BrandedTipStyle())
|
|
308
|
-
|
|
309
|
-
// Environment-wide (apply to a parent container)
|
|
310
|
-
NavigationStack {
|
|
311
|
-
ContentView()
|
|
312
|
-
}
|
|
313
|
-
.tipViewStyle(BrandedTipStyle())
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
### Minimal Compact Style
|
|
317
|
-
|
|
318
|
-
A stripped-down style for tips in tight layouts like toolbars or sidebars.
|
|
319
|
-
|
|
320
|
-
```swift
|
|
321
|
-
struct CompactTipStyle: TipViewStyle {
|
|
322
|
-
func makeBody(configuration: Configuration) -> some View {
|
|
323
|
-
HStack(spacing: 8) {
|
|
324
|
-
configuration.image?
|
|
325
|
-
.foregroundStyle(.tint)
|
|
326
|
-
|
|
327
|
-
configuration.title
|
|
328
|
-
.font(.caption.bold())
|
|
329
|
-
}
|
|
330
|
-
.padding(.horizontal, 12)
|
|
331
|
-
.padding(.vertical, 8)
|
|
332
|
-
.background(.tint.opacity(0.1), in: Capsule())
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
## TipGroup Sequencing
|
|
338
|
-
|
|
339
|
-
Use `TipGroup` to present a sequence of onboarding tips. Only the current
|
|
340
|
-
tip displays. When the user dismisses or acts on it, the next tip in the
|
|
341
|
-
group becomes current.
|
|
342
|
-
|
|
343
|
-
```swift
|
|
344
|
-
struct OnboardingTipA: Tip {
|
|
345
|
-
var title: Text { Text("Welcome to the App") }
|
|
346
|
-
var message: Text? { Text("Let's take a quick tour of the main features.") }
|
|
347
|
-
var image: Image? { Image(systemName: "hand.wave") }
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
struct OnboardingTipB: Tip {
|
|
351
|
-
var title: Text { Text("Browse Your Feed") }
|
|
352
|
-
var message: Text? { Text("Swipe through curated content tailored for you.") }
|
|
353
|
-
var image: Image? { Image(systemName: "rectangle.stack") }
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
struct OnboardingTipC: Tip {
|
|
357
|
-
var title: Text { Text("Customize Your Profile") }
|
|
358
|
-
var message: Text? { Text("Tap your avatar to set your name and preferences.") }
|
|
359
|
-
var image: Image? { Image(systemName: "person.crop.circle") }
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
struct HomeView: View {
|
|
363
|
-
let tipGroup = TipGroup(.ordered) {
|
|
364
|
-
OnboardingTipA()
|
|
365
|
-
OnboardingTipB()
|
|
366
|
-
OnboardingTipC()
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
var body: some View {
|
|
370
|
-
VStack(spacing: 20) {
|
|
371
|
-
if let currentTip = tipGroup.currentTip {
|
|
372
|
-
TipView(currentTip) { action in
|
|
373
|
-
currentTip.invalidate(reason: .actionPerformed)
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
FeedView()
|
|
378
|
-
}
|
|
379
|
-
.padding()
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
```
|
|
383
|
-
|
|
384
|
-
### Tip Group with Popover
|
|
385
|
-
|
|
386
|
-
Attach the group's current tip as a popover that moves between controls
|
|
387
|
-
as tips advance.
|
|
388
|
-
|
|
389
|
-
```swift
|
|
390
|
-
struct ToolbarGroupView: View {
|
|
391
|
-
let group = TipGroup(.ordered) {
|
|
392
|
-
SearchTip()
|
|
393
|
-
FilterTip()
|
|
394
|
-
SortTip()
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
var body: some View {
|
|
398
|
-
HStack {
|
|
399
|
-
Button("Search", systemImage: "magnifyingglass") { search() }
|
|
400
|
-
.popoverTip(group.currentTip as? SearchTip)
|
|
401
|
-
|
|
402
|
-
Button("Filter", systemImage: "line.3.horizontal.decrease") { filter() }
|
|
403
|
-
.popoverTip(group.currentTip as? FilterTip)
|
|
404
|
-
|
|
405
|
-
Button("Sort", systemImage: "arrow.up.arrow.down") { sort() }
|
|
406
|
-
.popoverTip(group.currentTip as? SortTip)
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
## Testing Strategies
|
|
413
|
-
|
|
414
|
-
### Previewing Tips in SwiftUI Previews
|
|
415
|
-
|
|
416
|
-
Configure TipKit in the preview body so tips display in Xcode previews.
|
|
417
|
-
Use `showAllTipsForTesting()` to bypass rules.
|
|
418
|
-
|
|
419
|
-
```swift
|
|
420
|
-
#Preview {
|
|
421
|
-
ContentView()
|
|
422
|
-
.task {
|
|
423
|
-
try? Tips.resetDatastore()
|
|
424
|
-
Tips.showAllTipsForTesting()
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
### Previewing a Specific Tip
|
|
430
|
-
|
|
431
|
-
Show only one tip in a focused preview.
|
|
432
|
-
|
|
433
|
-
```swift
|
|
434
|
-
#Preview("Favorite Tip") {
|
|
435
|
-
VStack {
|
|
436
|
-
TipView(FavoriteTip())
|
|
437
|
-
Spacer()
|
|
438
|
-
}
|
|
439
|
-
.padding()
|
|
440
|
-
.task {
|
|
441
|
-
try? Tips.resetDatastore()
|
|
442
|
-
Tips.showTipsForTesting([FavoriteTip.self])
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
```
|
|
446
|
-
|
|
447
|
-
### Unit Testing Tip Rules
|
|
448
|
-
|
|
449
|
-
Verify that parameter and event rules correctly control tip eligibility.
|
|
450
|
-
Reset the datastore before each test to ensure a clean state.
|
|
451
|
-
|
|
452
|
-
```swift
|
|
453
|
-
import XCTest
|
|
454
|
-
import TipKit
|
|
455
|
-
|
|
456
|
-
final class TipRuleTests: XCTestCase {
|
|
457
|
-
override func setUp() async throws {
|
|
458
|
-
try? Tips.resetDatastore()
|
|
459
|
-
try? Tips.configure()
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
override func tearDown() async throws {
|
|
463
|
-
try? Tips.resetDatastore()
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
func testAdvancedSearchTipRequiresLogin() async {
|
|
467
|
-
let tip = AdvancedSearchTip()
|
|
468
|
-
|
|
469
|
-
// Tip should not be eligible before login
|
|
470
|
-
AdvancedSearchTip.isLoggedIn = false
|
|
471
|
-
// Verify tip status
|
|
472
|
-
|
|
473
|
-
// Tip should become eligible after login + enough events
|
|
474
|
-
AdvancedSearchTip.isLoggedIn = true
|
|
475
|
-
for _ in 0..<3 {
|
|
476
|
-
AdvancedSearchTip.searchPerformed.donate()
|
|
477
|
-
}
|
|
478
|
-
// Verify tip status
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
func testTipInvalidation() async {
|
|
482
|
-
let tip = FavoriteTip()
|
|
483
|
-
tip.invalidate(reason: .actionPerformed)
|
|
484
|
-
// Tip should no longer be eligible after invalidation
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
```
|
|
488
|
-
|
|
489
|
-
### UI Testing with Forced Tips
|
|
490
|
-
|
|
491
|
-
Pass launch arguments to control tip visibility in UI tests. This ensures
|
|
492
|
-
tests that verify tip UI always see the tip, regardless of rules.
|
|
493
|
-
|
|
494
|
-
```swift
|
|
495
|
-
// In UI test setUp
|
|
496
|
-
let app = XCUIApplication()
|
|
497
|
-
app.launchArguments.append("--show-all-tips")
|
|
498
|
-
app.launch()
|
|
499
|
-
```
|
|
500
|
-
|
|
501
|
-
```swift
|
|
502
|
-
// In App.init
|
|
503
|
-
init() {
|
|
504
|
-
if ProcessInfo.processInfo.arguments.contains("--show-all-tips") {
|
|
505
|
-
Tips.showAllTipsForTesting()
|
|
506
|
-
}
|
|
507
|
-
if ProcessInfo.processInfo.arguments.contains("--hide-all-tips") {
|
|
508
|
-
Tips.hideAllTipsForTesting()
|
|
509
|
-
}
|
|
510
|
-
try? Tips.configure()
|
|
511
|
-
}
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
### UI Testing Without Tips
|
|
515
|
-
|
|
516
|
-
Suppress all tips in UI tests that are not about tip behavior, so tips
|
|
517
|
-
do not interfere with other test flows.
|
|
518
|
-
|
|
519
|
-
```swift
|
|
520
|
-
// In UI test setUp for non-tip tests
|
|
521
|
-
let app = XCUIApplication()
|
|
522
|
-
app.launchArguments.append("--hide-all-tips")
|
|
523
|
-
app.launch()
|
|
524
|
-
```
|
|
525
|
-
|
|
526
|
-
## Tip with Action Buttons
|
|
527
|
-
|
|
528
|
-
Add action buttons that deep-link to a feature. Invalidate the tip when
|
|
529
|
-
the user taps the primary action.
|
|
530
|
-
|
|
531
|
-
```swift
|
|
532
|
-
struct NewEditorTip: Tip {
|
|
533
|
-
var title: Text {
|
|
534
|
-
Text("Try the New Editor")
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
var message: Text? {
|
|
538
|
-
Text("A faster, more powerful editing experience awaits.")
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
var image: Image? {
|
|
542
|
-
Image(systemName: "pencil.and.outline")
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
var actions: [Action] {
|
|
546
|
-
Action(id: "open-editor", title: "Open Editor")
|
|
547
|
-
Action(id: "later", title: "Maybe Later")
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
struct HomeView: View {
|
|
552
|
-
let editorTip = NewEditorTip()
|
|
553
|
-
@State private var showEditor = false
|
|
554
|
-
|
|
555
|
-
var body: some View {
|
|
556
|
-
VStack {
|
|
557
|
-
TipView(editorTip) { action in
|
|
558
|
-
switch action.id {
|
|
559
|
-
case "open-editor":
|
|
560
|
-
showEditor = true
|
|
561
|
-
editorTip.invalidate(reason: .actionPerformed)
|
|
562
|
-
case "later":
|
|
563
|
-
editorTip.invalidate(reason: .tipClosed)
|
|
564
|
-
default:
|
|
565
|
-
break
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
MainContentView()
|
|
570
|
-
}
|
|
571
|
-
.sheet(isPresented: $showEditor) {
|
|
572
|
-
EditorView()
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
```
|
|
577
|
-
|
|
578
|
-
## Integration with Onboarding Flow
|
|
579
|
-
|
|
580
|
-
Coordinate TipKit with a first-run onboarding flow. Invalidate welcome
|
|
581
|
-
tips after the user completes onboarding so they do not see redundant
|
|
582
|
-
information.
|
|
583
|
-
|
|
584
|
-
```swift
|
|
585
|
-
struct WelcomeTip: Tip {
|
|
586
|
-
@Parameter
|
|
587
|
-
static var hasCompletedOnboarding: Bool = false
|
|
588
|
-
|
|
589
|
-
var title: Text { Text("Welcome to MyApp") }
|
|
590
|
-
var message: Text? { Text("Swipe through to learn the basics.") }
|
|
591
|
-
|
|
592
|
-
var rules: [Rule] {
|
|
593
|
-
// Only show if onboarding was NOT completed (user skipped it)
|
|
594
|
-
#Rule(Self.$hasCompletedOnboarding) { $0 == false }
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
struct FeatureDiscoveryTip: Tip {
|
|
599
|
-
@Parameter
|
|
600
|
-
static var hasCompletedOnboarding: Bool = false
|
|
601
|
-
|
|
602
|
-
var title: Text { Text("Discover Collections") }
|
|
603
|
-
var message: Text? { Text("Organize your items into collections for easy access.") }
|
|
604
|
-
|
|
605
|
-
var rules: [Rule] {
|
|
606
|
-
// Only show after onboarding completes
|
|
607
|
-
#Rule(Self.$hasCompletedOnboarding) { $0 == true }
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
struct OnboardingView: View {
|
|
612
|
-
@Binding var isPresented: Bool
|
|
613
|
-
|
|
614
|
-
var body: some View {
|
|
615
|
-
VStack {
|
|
616
|
-
// Onboarding pages...
|
|
617
|
-
|
|
618
|
-
Button("Get Started") {
|
|
619
|
-
completeOnboarding()
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
func completeOnboarding() {
|
|
625
|
-
// Invalidate welcome tips since onboarding covered the basics
|
|
626
|
-
WelcomeTip.hasCompletedOnboarding = true
|
|
627
|
-
FeatureDiscoveryTip.hasCompletedOnboarding = true
|
|
628
|
-
|
|
629
|
-
// Explicitly invalidate any welcome-specific tips
|
|
630
|
-
let welcomeTip = WelcomeTip()
|
|
631
|
-
welcomeTip.invalidate(reason: .actionPerformed)
|
|
632
|
-
|
|
633
|
-
isPresented = false
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
struct ContentView: View {
|
|
638
|
-
@AppStorage("hasCompletedOnboarding") private var hasCompletedOnboarding = false
|
|
639
|
-
@State private var showOnboarding = false
|
|
640
|
-
|
|
641
|
-
let welcomeTip = WelcomeTip()
|
|
642
|
-
let discoveryTip = FeatureDiscoveryTip()
|
|
643
|
-
|
|
644
|
-
var body: some View {
|
|
645
|
-
NavigationStack {
|
|
646
|
-
VStack {
|
|
647
|
-
TipView(welcomeTip)
|
|
648
|
-
|
|
649
|
-
CollectionGrid()
|
|
650
|
-
.popoverTip(discoveryTip)
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
.sheet(isPresented: $showOnboarding) {
|
|
654
|
-
OnboardingView(isPresented: $showOnboarding)
|
|
655
|
-
}
|
|
656
|
-
.onAppear {
|
|
657
|
-
if !hasCompletedOnboarding {
|
|
658
|
-
showOnboarding = true
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
```
|
|
664
|
-
|
|
665
|
-
## Full App Integration Example
|
|
666
|
-
|
|
667
|
-
A complete example showing TipKit configuration, multiple tips with rules,
|
|
668
|
-
a tip group, event donations, and proper invalidation.
|
|
669
|
-
|
|
670
|
-
```swift
|
|
671
|
-
import SwiftUI
|
|
672
|
-
import TipKit
|
|
673
|
-
|
|
674
|
-
// MARK: - Tips
|
|
675
|
-
|
|
676
|
-
struct SearchTip: Tip {
|
|
677
|
-
var title: Text { Text("Search Your Library") }
|
|
678
|
-
var message: Text? { Text("Tap to find any item by name, tag, or date.") }
|
|
679
|
-
var image: Image? { Image(systemName: "magnifyingglass") }
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
struct CollectionTip: Tip {
|
|
683
|
-
static let itemAddedEvent = Tips.Event(id: "itemAdded")
|
|
684
|
-
|
|
685
|
-
var title: Text { Text("Create a Collection") }
|
|
686
|
-
var message: Text? { Text("Group related items together for quick access.") }
|
|
687
|
-
var image: Image? { Image(systemName: "folder.badge.plus") }
|
|
688
|
-
|
|
689
|
-
var rules: [Rule] {
|
|
690
|
-
#Rule(Self.itemAddedEvent) { $0.donations.count >= 3 }
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
struct ShareTip: Tip {
|
|
695
|
-
@Parameter
|
|
696
|
-
static var hasCreatedCollection: Bool = false
|
|
697
|
-
|
|
698
|
-
var title: Text { Text("Share Your Collection") }
|
|
699
|
-
var message: Text? { Text("Invite others to view or collaborate on your collection.") }
|
|
700
|
-
var image: Image? { Image(systemName: "square.and.arrow.up") }
|
|
701
|
-
|
|
702
|
-
var rules: [Rule] {
|
|
703
|
-
#Rule(Self.$hasCreatedCollection) { $0 == true }
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
// MARK: - App
|
|
708
|
-
|
|
709
|
-
@main
|
|
710
|
-
struct LibraryApp: App {
|
|
711
|
-
init() {
|
|
712
|
-
#if DEBUG
|
|
713
|
-
if ProcessInfo.processInfo.arguments.contains("--show-all-tips") {
|
|
714
|
-
Tips.showAllTipsForTesting()
|
|
715
|
-
}
|
|
716
|
-
if ProcessInfo.processInfo.arguments.contains("--hide-all-tips") {
|
|
717
|
-
Tips.hideAllTipsForTesting()
|
|
718
|
-
}
|
|
719
|
-
#endif
|
|
720
|
-
|
|
721
|
-
try? Tips.configure([
|
|
722
|
-
.displayFrequency(.daily),
|
|
723
|
-
.datastoreLocation(.applicationDefault)
|
|
724
|
-
])
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
var body: some Scene {
|
|
728
|
-
WindowGroup { LibraryView() }
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
// MARK: - Main View
|
|
733
|
-
|
|
734
|
-
struct LibraryView: View {
|
|
735
|
-
let searchTip = SearchTip()
|
|
736
|
-
let collectionTip = CollectionTip()
|
|
737
|
-
let shareTip = ShareTip()
|
|
738
|
-
|
|
739
|
-
@State private var items: [LibraryItem] = []
|
|
740
|
-
|
|
741
|
-
var body: some View {
|
|
742
|
-
NavigationStack {
|
|
743
|
-
List {
|
|
744
|
-
TipView(collectionTip)
|
|
745
|
-
|
|
746
|
-
ForEach(items) { item in
|
|
747
|
-
Text(item.name)
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
.navigationTitle("Library")
|
|
751
|
-
.toolbar {
|
|
752
|
-
ToolbarItem(placement: .primaryAction) {
|
|
753
|
-
Button("Search", systemImage: "magnifyingglass") {
|
|
754
|
-
showSearch()
|
|
755
|
-
searchTip.invalidate(reason: .actionPerformed)
|
|
756
|
-
}
|
|
757
|
-
.popoverTip(searchTip)
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
ToolbarItem(placement: .secondaryAction) {
|
|
761
|
-
Button("Share", systemImage: "square.and.arrow.up") {
|
|
762
|
-
shareCollection()
|
|
763
|
-
shareTip.invalidate(reason: .actionPerformed)
|
|
764
|
-
}
|
|
765
|
-
.popoverTip(shareTip)
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
ToolbarItem(placement: .secondaryAction) {
|
|
769
|
-
Button("Add Item", systemImage: "plus") {
|
|
770
|
-
addItem()
|
|
771
|
-
CollectionTip.itemAddedEvent.donate()
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
func addItem() { /* ... */ }
|
|
779
|
-
func showSearch() { /* ... */ }
|
|
780
|
-
func shareCollection() { /* ... */ }
|
|
781
|
-
}
|
|
782
|
-
```
|
|
1
|
+
# TipKit Patterns Reference
|
|
2
|
+
|
|
3
|
+
Complete implementation patterns for TipKit including custom styles, event-based
|
|
4
|
+
rules, tip groups, testing strategies, onboarding flows, and SwiftUI previews.
|
|
5
|
+
All examples target iOS 17+ with Swift 6.2 conventions.
|
|
6
|
+
|
|
7
|
+
## Contents
|
|
8
|
+
|
|
9
|
+
- [Complete Tip with Rules and Events](#complete-tip-with-rules-and-events)
|
|
10
|
+
- [TipView and popoverTip Placement](#tipview-and-popovertip-placement)
|
|
11
|
+
- [Event-Based Rule with Donation Counting](#event-based-rule-with-donation-counting)
|
|
12
|
+
- [Custom TipViewStyle](#custom-tipviewstyle)
|
|
13
|
+
- [TipGroup Sequencing](#tipgroup-sequencing)
|
|
14
|
+
- [Testing Strategies](#testing-strategies)
|
|
15
|
+
- [Tip with Action Buttons](#tip-with-action-buttons)
|
|
16
|
+
- [Integration with Onboarding Flow](#integration-with-onboarding-flow)
|
|
17
|
+
- [Full App Integration Example](#full-app-integration-example)
|
|
18
|
+
|
|
19
|
+
## Complete Tip with Rules and Events
|
|
20
|
+
|
|
21
|
+
A full-featured tip combining parameter-based and event-based rules. The tip
|
|
22
|
+
appears only after the user has logged in and opened the app at least three
|
|
23
|
+
times, ensuring they are familiar with the basics before seeing advanced
|
|
24
|
+
feature discovery.
|
|
25
|
+
|
|
26
|
+
```swift
|
|
27
|
+
import TipKit
|
|
28
|
+
|
|
29
|
+
struct AdvancedSearchTip: Tip {
|
|
30
|
+
// Parameter rule: user must be logged in
|
|
31
|
+
@Parameter
|
|
32
|
+
static var isLoggedIn: Bool = false
|
|
33
|
+
|
|
34
|
+
// Event rule: user must have performed searches
|
|
35
|
+
static let searchPerformed = Tips.Event(id: "searchPerformed")
|
|
36
|
+
|
|
37
|
+
var title: Text {
|
|
38
|
+
Text("Try Advanced Search")
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
var message: Text? {
|
|
42
|
+
Text("Filter results by date, category, and location for faster discovery.")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
var image: Image? {
|
|
46
|
+
Image(systemName: "magnifyingglass")
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// All rules must pass before the tip becomes eligible
|
|
50
|
+
var rules: [Rule] {
|
|
51
|
+
#Rule(Self.$isLoggedIn) { $0 == true }
|
|
52
|
+
#Rule(Self.searchPerformed) { $0.donations.count >= 3 }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
var options: [TipOption] {
|
|
56
|
+
MaxDisplayCount(5)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Donating to Events
|
|
62
|
+
|
|
63
|
+
Place event donations at the point where the user action occurs. Each
|
|
64
|
+
donation increments the internal counter that rules evaluate against.
|
|
65
|
+
|
|
66
|
+
```swift
|
|
67
|
+
struct SearchView: View {
|
|
68
|
+
@State private var query = ""
|
|
69
|
+
|
|
70
|
+
var body: some View {
|
|
71
|
+
SearchBar(text: $query, onSubmit: {
|
|
72
|
+
performSearch(query)
|
|
73
|
+
// Donate each time the user searches
|
|
74
|
+
AdvancedSearchTip.searchPerformed.donate()
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Setting Parameters
|
|
81
|
+
|
|
82
|
+
Set parameter values when the relevant app state changes. Parameters persist
|
|
83
|
+
across launches via the TipKit datastore.
|
|
84
|
+
|
|
85
|
+
```swift
|
|
86
|
+
func handleLoginSuccess() {
|
|
87
|
+
AdvancedSearchTip.isLoggedIn = true
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## TipView and popoverTip Placement
|
|
92
|
+
|
|
93
|
+
### Inline TipView in a List
|
|
94
|
+
|
|
95
|
+
Place a `TipView` as a list row for contextual inline discovery. The tip
|
|
96
|
+
appears as part of the list content and animates away when dismissed or
|
|
97
|
+
invalidated.
|
|
98
|
+
|
|
99
|
+
```swift
|
|
100
|
+
struct ItemListView: View {
|
|
101
|
+
let filterTip = FilterTip()
|
|
102
|
+
@State private var items: [Item] = []
|
|
103
|
+
|
|
104
|
+
var body: some View {
|
|
105
|
+
List {
|
|
106
|
+
TipView(filterTip)
|
|
107
|
+
|
|
108
|
+
ForEach(items) { item in
|
|
109
|
+
ItemRow(item: item)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
.navigationTitle("Items")
|
|
113
|
+
.toolbar {
|
|
114
|
+
ToolbarItem(placement: .primaryAction) {
|
|
115
|
+
Button {
|
|
116
|
+
showFilters()
|
|
117
|
+
filterTip.invalidate(reason: .actionPerformed)
|
|
118
|
+
} label: {
|
|
119
|
+
Image(systemName: "line.3.horizontal.decrease.circle")
|
|
120
|
+
}
|
|
121
|
+
.popoverTip(filterTip, arrowEdge: .top)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Popover on Navigation Bar Button
|
|
129
|
+
|
|
130
|
+
Attach a popover tip to a toolbar button. The popover arrow points to the
|
|
131
|
+
button, drawing the user's attention to the exact control.
|
|
132
|
+
|
|
133
|
+
```swift
|
|
134
|
+
struct EditorView: View {
|
|
135
|
+
let undoTip = UndoShortcutTip()
|
|
136
|
+
|
|
137
|
+
var body: some View {
|
|
138
|
+
TextEditor(text: $text)
|
|
139
|
+
.toolbar {
|
|
140
|
+
ToolbarItem(placement: .primaryAction) {
|
|
141
|
+
Button("Undo", systemImage: "arrow.uturn.backward") {
|
|
142
|
+
undoLastAction()
|
|
143
|
+
undoTip.invalidate(reason: .actionPerformed)
|
|
144
|
+
}
|
|
145
|
+
.popoverTip(undoTip, arrowEdge: .top)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Popover on Tab Bar Item
|
|
153
|
+
|
|
154
|
+
Use `popoverTip` on a `Tab` label view inside a `TabView` to highlight a
|
|
155
|
+
new tab.
|
|
156
|
+
|
|
157
|
+
```swift
|
|
158
|
+
struct MainTabView: View {
|
|
159
|
+
let newTabTip = NewFeatureTabTip()
|
|
160
|
+
|
|
161
|
+
var body: some View {
|
|
162
|
+
TabView {
|
|
163
|
+
Tab("Home", systemImage: "house") {
|
|
164
|
+
HomeView()
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
Tab("Discover", systemImage: "sparkles") {
|
|
168
|
+
DiscoverView()
|
|
169
|
+
}
|
|
170
|
+
.popoverTip(newTabTip)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Event-Based Rule with Donation Counting
|
|
177
|
+
|
|
178
|
+
Track how many times the user performs an action, then show a tip suggesting
|
|
179
|
+
a more efficient alternative. This pattern is effective for progressive
|
|
180
|
+
disclosure: let users learn the basic workflow first, then reveal shortcuts.
|
|
181
|
+
|
|
182
|
+
```swift
|
|
183
|
+
struct KeyboardShortcutTip: Tip {
|
|
184
|
+
static let manualSaveEvent = Tips.Event(id: "manualSave")
|
|
185
|
+
|
|
186
|
+
var title: Text {
|
|
187
|
+
Text("Save Faster with Command-S")
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
var message: Text? {
|
|
191
|
+
Text("Press Command-S instead of using the menu to save your work instantly.")
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
var image: Image? {
|
|
195
|
+
Image(systemName: "keyboard")
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
var rules: [Rule] {
|
|
199
|
+
// Show after user has manually saved 5 times via button
|
|
200
|
+
#Rule(Self.manualSaveEvent) { $0.donations.count >= 5 }
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
struct DocumentView: View {
|
|
205
|
+
let shortcutTip = KeyboardShortcutTip()
|
|
206
|
+
|
|
207
|
+
var body: some View {
|
|
208
|
+
VStack {
|
|
209
|
+
TipView(shortcutTip)
|
|
210
|
+
DocumentEditor(document: $document)
|
|
211
|
+
}
|
|
212
|
+
.toolbar {
|
|
213
|
+
ToolbarItem {
|
|
214
|
+
Button("Save") {
|
|
215
|
+
saveDocument()
|
|
216
|
+
KeyboardShortcutTip.manualSaveEvent.donate()
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Event Donations with Associated Values
|
|
225
|
+
|
|
226
|
+
Attach a `DonationValue` to event donations for richer rule evaluation.
|
|
227
|
+
Use `Codable`-conforming types to provide context about each donation.
|
|
228
|
+
|
|
229
|
+
```swift
|
|
230
|
+
struct DetailedTip: Tip {
|
|
231
|
+
struct DonationInfo: Codable, Sendable {
|
|
232
|
+
let category: String
|
|
233
|
+
let timestamp: Date
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
static let itemViewed = Tips.Event<DonationInfo>(id: "itemViewed")
|
|
237
|
+
|
|
238
|
+
var rules: [Rule] {
|
|
239
|
+
#Rule(Self.itemViewed) {
|
|
240
|
+
$0.donations.filter {
|
|
241
|
+
$0.category == "premium"
|
|
242
|
+
}.count >= 3
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
var title: Text { Text("Unlock Premium Content") }
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Donate with associated value
|
|
250
|
+
DetailedTip.itemViewed.donate(
|
|
251
|
+
DetailedTip.DonationInfo(category: "premium", timestamp: .now)
|
|
252
|
+
)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Custom TipViewStyle
|
|
256
|
+
|
|
257
|
+
Create a branded tip appearance that matches the app's design language.
|
|
258
|
+
The `Configuration` provides access to the tip's title, message, image,
|
|
259
|
+
and actions.
|
|
260
|
+
|
|
261
|
+
```swift
|
|
262
|
+
struct BrandedTipStyle: TipViewStyle {
|
|
263
|
+
func makeBody(configuration: Configuration) -> some View {
|
|
264
|
+
HStack(alignment: .top, spacing: 16) {
|
|
265
|
+
configuration.image?
|
|
266
|
+
.font(.system(size: 24))
|
|
267
|
+
.foregroundStyle(.white)
|
|
268
|
+
.frame(width: 44, height: 44)
|
|
269
|
+
.background(.blue.gradient, in: RoundedRectangle(cornerRadius: 10))
|
|
270
|
+
|
|
271
|
+
VStack(alignment: .leading, spacing: 6) {
|
|
272
|
+
configuration.title
|
|
273
|
+
.font(.headline)
|
|
274
|
+
|
|
275
|
+
configuration.message?
|
|
276
|
+
.font(.subheadline)
|
|
277
|
+
.foregroundStyle(.secondary)
|
|
278
|
+
|
|
279
|
+
if !configuration.actions.isEmpty {
|
|
280
|
+
HStack(spacing: 12) {
|
|
281
|
+
ForEach(configuration.actions) { action in
|
|
282
|
+
Button(action: action.handler) {
|
|
283
|
+
action.label
|
|
284
|
+
.font(.subheadline.bold())
|
|
285
|
+
}
|
|
286
|
+
.buttonStyle(.bordered)
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
.padding(.top, 4)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
.padding()
|
|
294
|
+
.background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16))
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Applying the Custom Style
|
|
300
|
+
|
|
301
|
+
Apply the style to individual `TipView` instances or set it as the
|
|
302
|
+
environment default.
|
|
303
|
+
|
|
304
|
+
```swift
|
|
305
|
+
// Per view
|
|
306
|
+
TipView(myTip)
|
|
307
|
+
.tipViewStyle(BrandedTipStyle())
|
|
308
|
+
|
|
309
|
+
// Environment-wide (apply to a parent container)
|
|
310
|
+
NavigationStack {
|
|
311
|
+
ContentView()
|
|
312
|
+
}
|
|
313
|
+
.tipViewStyle(BrandedTipStyle())
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Minimal Compact Style
|
|
317
|
+
|
|
318
|
+
A stripped-down style for tips in tight layouts like toolbars or sidebars.
|
|
319
|
+
|
|
320
|
+
```swift
|
|
321
|
+
struct CompactTipStyle: TipViewStyle {
|
|
322
|
+
func makeBody(configuration: Configuration) -> some View {
|
|
323
|
+
HStack(spacing: 8) {
|
|
324
|
+
configuration.image?
|
|
325
|
+
.foregroundStyle(.tint)
|
|
326
|
+
|
|
327
|
+
configuration.title
|
|
328
|
+
.font(.caption.bold())
|
|
329
|
+
}
|
|
330
|
+
.padding(.horizontal, 12)
|
|
331
|
+
.padding(.vertical, 8)
|
|
332
|
+
.background(.tint.opacity(0.1), in: Capsule())
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## TipGroup Sequencing
|
|
338
|
+
|
|
339
|
+
Use `TipGroup` to present a sequence of onboarding tips. Only the current
|
|
340
|
+
tip displays. When the user dismisses or acts on it, the next tip in the
|
|
341
|
+
group becomes current.
|
|
342
|
+
|
|
343
|
+
```swift
|
|
344
|
+
struct OnboardingTipA: Tip {
|
|
345
|
+
var title: Text { Text("Welcome to the App") }
|
|
346
|
+
var message: Text? { Text("Let's take a quick tour of the main features.") }
|
|
347
|
+
var image: Image? { Image(systemName: "hand.wave") }
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
struct OnboardingTipB: Tip {
|
|
351
|
+
var title: Text { Text("Browse Your Feed") }
|
|
352
|
+
var message: Text? { Text("Swipe through curated content tailored for you.") }
|
|
353
|
+
var image: Image? { Image(systemName: "rectangle.stack") }
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
struct OnboardingTipC: Tip {
|
|
357
|
+
var title: Text { Text("Customize Your Profile") }
|
|
358
|
+
var message: Text? { Text("Tap your avatar to set your name and preferences.") }
|
|
359
|
+
var image: Image? { Image(systemName: "person.crop.circle") }
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
struct HomeView: View {
|
|
363
|
+
let tipGroup = TipGroup(.ordered) {
|
|
364
|
+
OnboardingTipA()
|
|
365
|
+
OnboardingTipB()
|
|
366
|
+
OnboardingTipC()
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
var body: some View {
|
|
370
|
+
VStack(spacing: 20) {
|
|
371
|
+
if let currentTip = tipGroup.currentTip {
|
|
372
|
+
TipView(currentTip) { action in
|
|
373
|
+
currentTip.invalidate(reason: .actionPerformed)
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
FeedView()
|
|
378
|
+
}
|
|
379
|
+
.padding()
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### Tip Group with Popover
|
|
385
|
+
|
|
386
|
+
Attach the group's current tip as a popover that moves between controls
|
|
387
|
+
as tips advance.
|
|
388
|
+
|
|
389
|
+
```swift
|
|
390
|
+
struct ToolbarGroupView: View {
|
|
391
|
+
let group = TipGroup(.ordered) {
|
|
392
|
+
SearchTip()
|
|
393
|
+
FilterTip()
|
|
394
|
+
SortTip()
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
var body: some View {
|
|
398
|
+
HStack {
|
|
399
|
+
Button("Search", systemImage: "magnifyingglass") { search() }
|
|
400
|
+
.popoverTip(group.currentTip as? SearchTip)
|
|
401
|
+
|
|
402
|
+
Button("Filter", systemImage: "line.3.horizontal.decrease") { filter() }
|
|
403
|
+
.popoverTip(group.currentTip as? FilterTip)
|
|
404
|
+
|
|
405
|
+
Button("Sort", systemImage: "arrow.up.arrow.down") { sort() }
|
|
406
|
+
.popoverTip(group.currentTip as? SortTip)
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
## Testing Strategies
|
|
413
|
+
|
|
414
|
+
### Previewing Tips in SwiftUI Previews
|
|
415
|
+
|
|
416
|
+
Configure TipKit in the preview body so tips display in Xcode previews.
|
|
417
|
+
Use `showAllTipsForTesting()` to bypass rules.
|
|
418
|
+
|
|
419
|
+
```swift
|
|
420
|
+
#Preview {
|
|
421
|
+
ContentView()
|
|
422
|
+
.task {
|
|
423
|
+
try? Tips.resetDatastore()
|
|
424
|
+
Tips.showAllTipsForTesting()
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Previewing a Specific Tip
|
|
430
|
+
|
|
431
|
+
Show only one tip in a focused preview.
|
|
432
|
+
|
|
433
|
+
```swift
|
|
434
|
+
#Preview("Favorite Tip") {
|
|
435
|
+
VStack {
|
|
436
|
+
TipView(FavoriteTip())
|
|
437
|
+
Spacer()
|
|
438
|
+
}
|
|
439
|
+
.padding()
|
|
440
|
+
.task {
|
|
441
|
+
try? Tips.resetDatastore()
|
|
442
|
+
Tips.showTipsForTesting([FavoriteTip.self])
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Unit Testing Tip Rules
|
|
448
|
+
|
|
449
|
+
Verify that parameter and event rules correctly control tip eligibility.
|
|
450
|
+
Reset the datastore before each test to ensure a clean state.
|
|
451
|
+
|
|
452
|
+
```swift
|
|
453
|
+
import XCTest
|
|
454
|
+
import TipKit
|
|
455
|
+
|
|
456
|
+
final class TipRuleTests: XCTestCase {
|
|
457
|
+
override func setUp() async throws {
|
|
458
|
+
try? Tips.resetDatastore()
|
|
459
|
+
try? Tips.configure()
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
override func tearDown() async throws {
|
|
463
|
+
try? Tips.resetDatastore()
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
func testAdvancedSearchTipRequiresLogin() async {
|
|
467
|
+
let tip = AdvancedSearchTip()
|
|
468
|
+
|
|
469
|
+
// Tip should not be eligible before login
|
|
470
|
+
AdvancedSearchTip.isLoggedIn = false
|
|
471
|
+
// Verify tip status
|
|
472
|
+
|
|
473
|
+
// Tip should become eligible after login + enough events
|
|
474
|
+
AdvancedSearchTip.isLoggedIn = true
|
|
475
|
+
for _ in 0..<3 {
|
|
476
|
+
AdvancedSearchTip.searchPerformed.donate()
|
|
477
|
+
}
|
|
478
|
+
// Verify tip status
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
func testTipInvalidation() async {
|
|
482
|
+
let tip = FavoriteTip()
|
|
483
|
+
tip.invalidate(reason: .actionPerformed)
|
|
484
|
+
// Tip should no longer be eligible after invalidation
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### UI Testing with Forced Tips
|
|
490
|
+
|
|
491
|
+
Pass launch arguments to control tip visibility in UI tests. This ensures
|
|
492
|
+
tests that verify tip UI always see the tip, regardless of rules.
|
|
493
|
+
|
|
494
|
+
```swift
|
|
495
|
+
// In UI test setUp
|
|
496
|
+
let app = XCUIApplication()
|
|
497
|
+
app.launchArguments.append("--show-all-tips")
|
|
498
|
+
app.launch()
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
```swift
|
|
502
|
+
// In App.init
|
|
503
|
+
init() {
|
|
504
|
+
if ProcessInfo.processInfo.arguments.contains("--show-all-tips") {
|
|
505
|
+
Tips.showAllTipsForTesting()
|
|
506
|
+
}
|
|
507
|
+
if ProcessInfo.processInfo.arguments.contains("--hide-all-tips") {
|
|
508
|
+
Tips.hideAllTipsForTesting()
|
|
509
|
+
}
|
|
510
|
+
try? Tips.configure()
|
|
511
|
+
}
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### UI Testing Without Tips
|
|
515
|
+
|
|
516
|
+
Suppress all tips in UI tests that are not about tip behavior, so tips
|
|
517
|
+
do not interfere with other test flows.
|
|
518
|
+
|
|
519
|
+
```swift
|
|
520
|
+
// In UI test setUp for non-tip tests
|
|
521
|
+
let app = XCUIApplication()
|
|
522
|
+
app.launchArguments.append("--hide-all-tips")
|
|
523
|
+
app.launch()
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
## Tip with Action Buttons
|
|
527
|
+
|
|
528
|
+
Add action buttons that deep-link to a feature. Invalidate the tip when
|
|
529
|
+
the user taps the primary action.
|
|
530
|
+
|
|
531
|
+
```swift
|
|
532
|
+
struct NewEditorTip: Tip {
|
|
533
|
+
var title: Text {
|
|
534
|
+
Text("Try the New Editor")
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
var message: Text? {
|
|
538
|
+
Text("A faster, more powerful editing experience awaits.")
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
var image: Image? {
|
|
542
|
+
Image(systemName: "pencil.and.outline")
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
var actions: [Action] {
|
|
546
|
+
Action(id: "open-editor", title: "Open Editor")
|
|
547
|
+
Action(id: "later", title: "Maybe Later")
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
struct HomeView: View {
|
|
552
|
+
let editorTip = NewEditorTip()
|
|
553
|
+
@State private var showEditor = false
|
|
554
|
+
|
|
555
|
+
var body: some View {
|
|
556
|
+
VStack {
|
|
557
|
+
TipView(editorTip) { action in
|
|
558
|
+
switch action.id {
|
|
559
|
+
case "open-editor":
|
|
560
|
+
showEditor = true
|
|
561
|
+
editorTip.invalidate(reason: .actionPerformed)
|
|
562
|
+
case "later":
|
|
563
|
+
editorTip.invalidate(reason: .tipClosed)
|
|
564
|
+
default:
|
|
565
|
+
break
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
MainContentView()
|
|
570
|
+
}
|
|
571
|
+
.sheet(isPresented: $showEditor) {
|
|
572
|
+
EditorView()
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
## Integration with Onboarding Flow
|
|
579
|
+
|
|
580
|
+
Coordinate TipKit with a first-run onboarding flow. Invalidate welcome
|
|
581
|
+
tips after the user completes onboarding so they do not see redundant
|
|
582
|
+
information.
|
|
583
|
+
|
|
584
|
+
```swift
|
|
585
|
+
struct WelcomeTip: Tip {
|
|
586
|
+
@Parameter
|
|
587
|
+
static var hasCompletedOnboarding: Bool = false
|
|
588
|
+
|
|
589
|
+
var title: Text { Text("Welcome to MyApp") }
|
|
590
|
+
var message: Text? { Text("Swipe through to learn the basics.") }
|
|
591
|
+
|
|
592
|
+
var rules: [Rule] {
|
|
593
|
+
// Only show if onboarding was NOT completed (user skipped it)
|
|
594
|
+
#Rule(Self.$hasCompletedOnboarding) { $0 == false }
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
struct FeatureDiscoveryTip: Tip {
|
|
599
|
+
@Parameter
|
|
600
|
+
static var hasCompletedOnboarding: Bool = false
|
|
601
|
+
|
|
602
|
+
var title: Text { Text("Discover Collections") }
|
|
603
|
+
var message: Text? { Text("Organize your items into collections for easy access.") }
|
|
604
|
+
|
|
605
|
+
var rules: [Rule] {
|
|
606
|
+
// Only show after onboarding completes
|
|
607
|
+
#Rule(Self.$hasCompletedOnboarding) { $0 == true }
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
struct OnboardingView: View {
|
|
612
|
+
@Binding var isPresented: Bool
|
|
613
|
+
|
|
614
|
+
var body: some View {
|
|
615
|
+
VStack {
|
|
616
|
+
// Onboarding pages...
|
|
617
|
+
|
|
618
|
+
Button("Get Started") {
|
|
619
|
+
completeOnboarding()
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
func completeOnboarding() {
|
|
625
|
+
// Invalidate welcome tips since onboarding covered the basics
|
|
626
|
+
WelcomeTip.hasCompletedOnboarding = true
|
|
627
|
+
FeatureDiscoveryTip.hasCompletedOnboarding = true
|
|
628
|
+
|
|
629
|
+
// Explicitly invalidate any welcome-specific tips
|
|
630
|
+
let welcomeTip = WelcomeTip()
|
|
631
|
+
welcomeTip.invalidate(reason: .actionPerformed)
|
|
632
|
+
|
|
633
|
+
isPresented = false
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
struct ContentView: View {
|
|
638
|
+
@AppStorage("hasCompletedOnboarding") private var hasCompletedOnboarding = false
|
|
639
|
+
@State private var showOnboarding = false
|
|
640
|
+
|
|
641
|
+
let welcomeTip = WelcomeTip()
|
|
642
|
+
let discoveryTip = FeatureDiscoveryTip()
|
|
643
|
+
|
|
644
|
+
var body: some View {
|
|
645
|
+
NavigationStack {
|
|
646
|
+
VStack {
|
|
647
|
+
TipView(welcomeTip)
|
|
648
|
+
|
|
649
|
+
CollectionGrid()
|
|
650
|
+
.popoverTip(discoveryTip)
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
.sheet(isPresented: $showOnboarding) {
|
|
654
|
+
OnboardingView(isPresented: $showOnboarding)
|
|
655
|
+
}
|
|
656
|
+
.onAppear {
|
|
657
|
+
if !hasCompletedOnboarding {
|
|
658
|
+
showOnboarding = true
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
## Full App Integration Example
|
|
666
|
+
|
|
667
|
+
A complete example showing TipKit configuration, multiple tips with rules,
|
|
668
|
+
a tip group, event donations, and proper invalidation.
|
|
669
|
+
|
|
670
|
+
```swift
|
|
671
|
+
import SwiftUI
|
|
672
|
+
import TipKit
|
|
673
|
+
|
|
674
|
+
// MARK: - Tips
|
|
675
|
+
|
|
676
|
+
struct SearchTip: Tip {
|
|
677
|
+
var title: Text { Text("Search Your Library") }
|
|
678
|
+
var message: Text? { Text("Tap to find any item by name, tag, or date.") }
|
|
679
|
+
var image: Image? { Image(systemName: "magnifyingglass") }
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
struct CollectionTip: Tip {
|
|
683
|
+
static let itemAddedEvent = Tips.Event(id: "itemAdded")
|
|
684
|
+
|
|
685
|
+
var title: Text { Text("Create a Collection") }
|
|
686
|
+
var message: Text? { Text("Group related items together for quick access.") }
|
|
687
|
+
var image: Image? { Image(systemName: "folder.badge.plus") }
|
|
688
|
+
|
|
689
|
+
var rules: [Rule] {
|
|
690
|
+
#Rule(Self.itemAddedEvent) { $0.donations.count >= 3 }
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
struct ShareTip: Tip {
|
|
695
|
+
@Parameter
|
|
696
|
+
static var hasCreatedCollection: Bool = false
|
|
697
|
+
|
|
698
|
+
var title: Text { Text("Share Your Collection") }
|
|
699
|
+
var message: Text? { Text("Invite others to view or collaborate on your collection.") }
|
|
700
|
+
var image: Image? { Image(systemName: "square.and.arrow.up") }
|
|
701
|
+
|
|
702
|
+
var rules: [Rule] {
|
|
703
|
+
#Rule(Self.$hasCreatedCollection) { $0 == true }
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// MARK: - App
|
|
708
|
+
|
|
709
|
+
@main
|
|
710
|
+
struct LibraryApp: App {
|
|
711
|
+
init() {
|
|
712
|
+
#if DEBUG
|
|
713
|
+
if ProcessInfo.processInfo.arguments.contains("--show-all-tips") {
|
|
714
|
+
Tips.showAllTipsForTesting()
|
|
715
|
+
}
|
|
716
|
+
if ProcessInfo.processInfo.arguments.contains("--hide-all-tips") {
|
|
717
|
+
Tips.hideAllTipsForTesting()
|
|
718
|
+
}
|
|
719
|
+
#endif
|
|
720
|
+
|
|
721
|
+
try? Tips.configure([
|
|
722
|
+
.displayFrequency(.daily),
|
|
723
|
+
.datastoreLocation(.applicationDefault)
|
|
724
|
+
])
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
var body: some Scene {
|
|
728
|
+
WindowGroup { LibraryView() }
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// MARK: - Main View
|
|
733
|
+
|
|
734
|
+
struct LibraryView: View {
|
|
735
|
+
let searchTip = SearchTip()
|
|
736
|
+
let collectionTip = CollectionTip()
|
|
737
|
+
let shareTip = ShareTip()
|
|
738
|
+
|
|
739
|
+
@State private var items: [LibraryItem] = []
|
|
740
|
+
|
|
741
|
+
var body: some View {
|
|
742
|
+
NavigationStack {
|
|
743
|
+
List {
|
|
744
|
+
TipView(collectionTip)
|
|
745
|
+
|
|
746
|
+
ForEach(items) { item in
|
|
747
|
+
Text(item.name)
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
.navigationTitle("Library")
|
|
751
|
+
.toolbar {
|
|
752
|
+
ToolbarItem(placement: .primaryAction) {
|
|
753
|
+
Button("Search", systemImage: "magnifyingglass") {
|
|
754
|
+
showSearch()
|
|
755
|
+
searchTip.invalidate(reason: .actionPerformed)
|
|
756
|
+
}
|
|
757
|
+
.popoverTip(searchTip)
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
ToolbarItem(placement: .secondaryAction) {
|
|
761
|
+
Button("Share", systemImage: "square.and.arrow.up") {
|
|
762
|
+
shareCollection()
|
|
763
|
+
shareTip.invalidate(reason: .actionPerformed)
|
|
764
|
+
}
|
|
765
|
+
.popoverTip(shareTip)
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
ToolbarItem(placement: .secondaryAction) {
|
|
769
|
+
Button("Add Item", systemImage: "plus") {
|
|
770
|
+
addItem()
|
|
771
|
+
CollectionTip.itemAddedEvent.donate()
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
func addItem() { /* ... */ }
|
|
779
|
+
func showSearch() { /* ... */ }
|
|
780
|
+
func shareCollection() { /* ... */ }
|
|
781
|
+
}
|
|
782
|
+
```
|