@devo-bmad-custom/agent-orchestration 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/installer.js +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 +85 -32
- package/src/_memory/skills/nimbalyst-tracking/SKILL.md +103 -103
- package/src/_memory/skills/writing-skills/SKILL.md +655 -655
- package/src/bmb/agents/agent-builder.md +59 -59
- package/src/bmb/agents/module-builder.md +60 -60
- package/src/bmb/agents/workflow-builder.md +61 -61
- package/src/bmb/config.yaml +12 -12
- package/src/bmb/module-help.csv +13 -13
- package/src/bmb/workflows/agent/data/agent-architecture.md +258 -258
- package/src/bmb/workflows/agent/data/agent-compilation.md +185 -185
- package/src/bmb/workflows/agent/data/agent-menu-patterns.md +189 -189
- package/src/bmb/workflows/agent/data/agent-metadata.md +133 -133
- package/src/bmb/workflows/agent/data/agent-validation.md +111 -111
- package/src/bmb/workflows/agent/data/brainstorm-context.md +96 -96
- package/src/bmb/workflows/agent/data/communication-presets.csv +61 -61
- package/src/bmb/workflows/agent/data/critical-actions.md +75 -75
- package/src/bmb/workflows/agent/data/persona-properties.md +252 -252
- package/src/bmb/workflows/agent/data/principles-crafting.md +142 -142
- package/src/bmb/workflows/agent/data/reference/module-examples/architect.md +68 -68
- package/src/bmb/workflows/agent/data/reference/with-sidecar/journal-keeper/journal-keeper-sidecar/entries/yy-mm-dd-entry-template.md +16 -16
- package/src/bmb/workflows/agent/data/understanding-agent-types.md +126 -126
- package/src/bmb/workflows/agent/steps-c/step-01-brainstorm.md +129 -129
- package/src/bmb/workflows/agent/steps-c/step-02-discovery.md +170 -170
- package/src/bmb/workflows/agent/steps-c/step-03-sidecar-metadata.md +309 -309
- package/src/bmb/workflows/agent/steps-c/step-04-persona.md +213 -213
- package/src/bmb/workflows/agent/steps-c/step-05-commands-menu.md +179 -179
- package/src/bmb/workflows/agent/steps-c/step-06-activation.md +278 -278
- package/src/bmb/workflows/agent/steps-c/step-07-build-agent.md +316 -316
- package/src/bmb/workflows/agent/steps-c/step-08-celebrate.md +247 -247
- package/src/bmb/workflows/agent/steps-e/e-01-load-existing.md +221 -221
- package/src/bmb/workflows/agent/steps-e/e-02-discover-edits.md +195 -195
- package/src/bmb/workflows/agent/steps-e/e-04-sidecar-metadata.md +126 -126
- package/src/bmb/workflows/agent/steps-e/e-05-persona.md +135 -135
- package/src/bmb/workflows/agent/steps-e/e-06-commands-menu.md +123 -123
- package/src/bmb/workflows/agent/steps-e/e-07-activation.md +124 -124
- package/src/bmb/workflows/agent/steps-e/e-08-edit-agent.md +197 -197
- package/src/bmb/workflows/agent/steps-e/e-09-celebrate.md +155 -155
- package/src/bmb/workflows/agent/steps-v/v-01-load-review.md +137 -137
- package/src/bmb/workflows/agent/steps-v/v-02a-validate-metadata.md +116 -116
- package/src/bmb/workflows/agent/steps-v/v-02b-validate-persona.md +124 -124
- package/src/bmb/workflows/agent/steps-v/v-02c-validate-menu.md +127 -127
- package/src/bmb/workflows/agent/steps-v/v-02d-validate-structure.md +134 -134
- package/src/bmb/workflows/agent/steps-v/v-02e-validate-sidecar.md +134 -134
- package/src/bmb/workflows/agent/steps-v/v-03-summary.md +104 -104
- package/src/bmb/workflows/agent/templates/agent-plan.template.md +5 -5
- package/src/bmb/workflows/agent/templates/agent-template.md +89 -89
- package/src/bmb/workflows/agent/workflow-create-agent.md +72 -72
- package/src/bmb/workflows/agent/workflow-edit-agent.md +75 -75
- package/src/bmb/workflows/agent/workflow-validate-agent.md +73 -73
- package/src/bmb/workflows/module/data/agent-architecture.md +179 -179
- package/src/bmb/workflows/module/data/agent-spec-template.md +79 -79
- package/src/bmb/workflows/module/data/module-standards.md +263 -263
- package/src/bmb/workflows/module/data/module-yaml-conventions.md +392 -392
- package/src/bmb/workflows/module/module-help-generate.md +254 -254
- package/src/bmb/workflows/module/steps-b/step-01-welcome.md +148 -148
- package/src/bmb/workflows/module/steps-b/step-02-spark.md +141 -141
- package/src/bmb/workflows/module/steps-b/step-03-module-type.md +149 -149
- package/src/bmb/workflows/module/steps-b/step-04-vision.md +83 -83
- package/src/bmb/workflows/module/steps-b/step-05-identity.md +97 -97
- package/src/bmb/workflows/module/steps-b/step-06-users.md +86 -86
- package/src/bmb/workflows/module/steps-b/step-07-value.md +76 -76
- package/src/bmb/workflows/module/steps-b/step-08-agents.md +97 -97
- package/src/bmb/workflows/module/steps-b/step-09-workflows.md +83 -83
- package/src/bmb/workflows/module/steps-b/step-10-tools.md +91 -91
- package/src/bmb/workflows/module/steps-b/step-11-scenarios.md +84 -84
- package/src/bmb/workflows/module/steps-b/step-12-creative.md +95 -95
- package/src/bmb/workflows/module/steps-b/step-13-review.md +105 -105
- package/src/bmb/workflows/module/steps-b/step-14-finalize.md +117 -117
- package/src/bmb/workflows/module/steps-c/step-01-load-brief.md +179 -179
- package/src/bmb/workflows/module/steps-c/step-01b-continue.md +82 -82
- package/src/bmb/workflows/module/steps-c/step-02-structure.md +105 -105
- package/src/bmb/workflows/module/steps-c/step-03-config.md +119 -119
- package/src/bmb/workflows/module/steps-c/step-04-agents.md +168 -168
- package/src/bmb/workflows/module/steps-c/step-05-workflows.md +184 -184
- package/src/bmb/workflows/module/steps-c/step-06-docs.md +401 -401
- package/src/bmb/workflows/module/steps-c/step-07-complete.md +152 -152
- package/src/bmb/workflows/module/steps-e/step-01-load-target.md +81 -81
- package/src/bmb/workflows/module/steps-e/step-02-select-edit.md +77 -77
- package/src/bmb/workflows/module/steps-e/step-03-apply-edit.md +77 -77
- package/src/bmb/workflows/module/steps-e/step-04-review.md +80 -80
- package/src/bmb/workflows/module/steps-e/step-05-confirm.md +75 -75
- package/src/bmb/workflows/module/steps-v/step-01-load-target.md +96 -96
- package/src/bmb/workflows/module/steps-v/step-02-file-structure.md +93 -93
- package/src/bmb/workflows/module/steps-v/step-03-module-yaml.md +99 -99
- package/src/bmb/workflows/module/steps-v/step-04-agent-specs.md +152 -152
- package/src/bmb/workflows/module/steps-v/step-05-workflow-specs.md +152 -152
- package/src/bmb/workflows/module/steps-v/step-06-documentation.md +143 -143
- package/src/bmb/workflows/module/steps-v/step-07-installation.md +102 -102
- package/src/bmb/workflows/module/steps-v/step-08-report.md +197 -197
- package/src/bmb/workflows/module/templates/brief-template.md +154 -154
- package/src/bmb/workflows/module/templates/workflow-spec-template.md +96 -96
- package/src/bmb/workflows/module/workflow-create-module-brief.md +71 -71
- package/src/bmb/workflows/module/workflow-create-module.md +86 -86
- package/src/bmb/workflows/module/workflow-edit-module.md +66 -66
- package/src/bmb/workflows/module/workflow-validate-module.md +66 -66
- package/src/bmb/workflows/workflow/data/architecture.md +150 -150
- package/src/bmb/workflows/workflow/data/common-workflow-tools.csv +19 -19
- package/src/bmb/workflows/workflow/data/csv-data-file-standards.md +53 -53
- package/src/bmb/workflows/workflow/data/frontmatter-standards.md +184 -184
- package/src/bmb/workflows/workflow/data/input-discovery-standards.md +191 -191
- package/src/bmb/workflows/workflow/data/intent-vs-prescriptive-spectrum.md +44 -44
- package/src/bmb/workflows/workflow/data/menu-handling-standards.md +133 -133
- package/src/bmb/workflows/workflow/data/output-format-standards.md +135 -135
- package/src/bmb/workflows/workflow/data/step-file-rules.md +235 -235
- package/src/bmb/workflows/workflow/data/step-type-patterns.md +257 -257
- package/src/bmb/workflows/workflow/data/subprocess-optimization-patterns.md +188 -188
- package/src/bmb/workflows/workflow/data/trimodal-workflow-structure.md +164 -164
- package/src/bmb/workflows/workflow/data/workflow-chaining-standards.md +222 -222
- package/src/bmb/workflows/workflow/data/workflow-examples.md +232 -232
- package/src/bmb/workflows/workflow/data/workflow-type-criteria.md +134 -134
- package/src/bmb/workflows/workflow/steps-c/step-00-conversion.md +263 -263
- package/src/bmb/workflows/workflow/steps-c/step-01-discovery.md +194 -194
- package/src/bmb/workflows/workflow/steps-c/step-01b-continuation.md +3 -3
- package/src/bmb/workflows/workflow/steps-c/step-02-classification.md +270 -270
- package/src/bmb/workflows/workflow/steps-c/step-03-requirements.md +283 -283
- package/src/bmb/workflows/workflow/steps-c/step-04-tools.md +282 -282
- package/src/bmb/workflows/workflow/steps-c/step-05-plan-review.md +243 -243
- package/src/bmb/workflows/workflow/steps-c/step-06-design.md +330 -330
- package/src/bmb/workflows/workflow/steps-c/step-07-foundation.md +239 -239
- package/src/bmb/workflows/workflow/steps-c/step-08-build-step-01.md +379 -379
- package/src/bmb/workflows/workflow/steps-c/step-09-build-next-step.md +350 -350
- package/src/bmb/workflows/workflow/steps-c/step-10-confirmation.md +322 -322
- package/src/bmb/workflows/workflow/steps-c/step-11-completion.md +191 -191
- package/src/bmb/workflows/workflow/steps-e/step-e-01-assess-workflow.md +237 -237
- package/src/bmb/workflows/workflow/steps-e/step-e-02-discover-edits.md +251 -251
- package/src/bmb/workflows/workflow/steps-e/step-e-03-fix-validation.md +254 -254
- package/src/bmb/workflows/workflow/steps-e/step-e-04-direct-edit.md +277 -277
- package/src/bmb/workflows/workflow/steps-e/step-e-05-apply-edit.md +154 -154
- package/src/bmb/workflows/workflow/steps-e/step-e-06-validate-after.md +190 -190
- package/src/bmb/workflows/workflow/steps-e/step-e-07-complete.md +206 -206
- package/src/bmb/workflows/workflow/steps-v/step-01-validate-max-mode.md +109 -109
- package/src/bmb/workflows/workflow/steps-v/step-01-validate.md +221 -221
- package/src/bmb/workflows/workflow/steps-v/step-01b-structure.md +152 -152
- package/src/bmb/workflows/workflow/steps-v/step-02-frontmatter-validation.md +199 -199
- package/src/bmb/workflows/workflow/steps-v/step-02b-path-violations.md +265 -265
- package/src/bmb/workflows/workflow/steps-v/step-03-menu-validation.md +164 -164
- package/src/bmb/workflows/workflow/steps-v/step-04-step-type-validation.md +211 -211
- package/src/bmb/workflows/workflow/steps-v/step-05-output-format-validation.md +200 -200
- package/src/bmb/workflows/workflow/steps-v/step-06-validation-design-check.md +195 -195
- package/src/bmb/workflows/workflow/steps-v/step-07-instruction-style-check.md +209 -209
- package/src/bmb/workflows/workflow/steps-v/step-08-collaborative-experience-check.md +199 -199
- package/src/bmb/workflows/workflow/steps-v/step-08b-subprocess-optimization.md +179 -179
- package/src/bmb/workflows/workflow/steps-v/step-09-cohesive-review.md +186 -186
- package/src/bmb/workflows/workflow/steps-v/step-10-report-complete.md +154 -154
- package/src/bmb/workflows/workflow/steps-v/step-11-plan-validation.md +237 -237
- package/src/bmb/workflows/workflow/templates/minimal-output-template.md +11 -11
- package/src/bmb/workflows/workflow/templates/step-01-init-continuable-template.md +241 -241
- package/src/bmb/workflows/workflow/templates/step-1b-template.md +224 -224
- package/src/bmb/workflows/workflow/templates/step-template.md +294 -294
- package/src/bmb/workflows/workflow/templates/workflow-template.md +102 -102
- package/src/bmb/workflows/workflow/workflow-create-workflow.md +79 -79
- package/src/bmb/workflows/workflow/workflow-edit-workflow.md +65 -65
- package/src/bmb/workflows/workflow/workflow-rework-workflow.md +65 -65
- package/src/bmb/workflows/workflow/workflow-validate-max-parallel-workflow.md +66 -66
- package/src/bmb/workflows/workflow/workflow-validate-workflow.md +65 -65
- package/src/bmm/agents/analyst.md +104 -104
- package/src/bmm/agents/dev.md +100 -100
- package/src/bmm/agents/qa.md +100 -90
- package/src/bmm/agents/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,780 +1,780 @@
|
|
|
1
|
-
# Design Polish
|
|
2
|
-
|
|
3
|
-
## Contents
|
|
4
|
-
- [HIG Alignment](#hig-alignment)
|
|
5
|
-
- [Theming and Dynamic Type](#theming-and-dynamic-type)
|
|
6
|
-
- [Haptics](#haptics)
|
|
7
|
-
- [Matched Transitions](#matched-transitions)
|
|
8
|
-
- [Loading and Placeholders](#loading-and-placeholders)
|
|
9
|
-
- [Focus Handling](#focus-handling)
|
|
10
|
-
|
|
11
|
-
## HIG Alignment
|
|
12
|
-
|
|
13
|
-
iOS Human Interface Guidelines patterns for layout, typography, color, accessibility, and feedback in SwiftUI.
|
|
14
|
-
|
|
15
|
-
### Contents
|
|
16
|
-
|
|
17
|
-
- [Layout and Spacing](#layout-and-spacing)
|
|
18
|
-
- [Typography](#typography)
|
|
19
|
-
- [Color System](#color-system)
|
|
20
|
-
- [Navigation Patterns](#navigation-patterns)
|
|
21
|
-
- [Feedback](#feedback)
|
|
22
|
-
- [Accessibility](#accessibility)
|
|
23
|
-
- [Error and Empty States](#error-and-empty-states)
|
|
24
|
-
|
|
25
|
-
### Layout and Spacing
|
|
26
|
-
|
|
27
|
-
#### Standard Margins
|
|
28
|
-
|
|
29
|
-
```swift
|
|
30
|
-
private let standardMargin: CGFloat = 16
|
|
31
|
-
private let compactMargin: CGFloat = 8
|
|
32
|
-
private let largeMargin: CGFloat = 24
|
|
33
|
-
|
|
34
|
-
extension EdgeInsets {
|
|
35
|
-
static let standard = EdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16)
|
|
36
|
-
static let listRow = EdgeInsets(top: 12, leading: 16, bottom: 12, trailing: 16)
|
|
37
|
-
}
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
#### Safe Area Handling
|
|
41
|
-
|
|
42
|
-
```swift
|
|
43
|
-
ScrollView {
|
|
44
|
-
LazyVStack(spacing: 16) {
|
|
45
|
-
ForEach(items) { item in
|
|
46
|
-
ItemRow(item: item)
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
.padding(.horizontal)
|
|
50
|
-
}
|
|
51
|
-
.safeAreaInset(edge: .bottom) {
|
|
52
|
-
HStack {
|
|
53
|
-
Button("Cancel") { }
|
|
54
|
-
.buttonStyle(.bordered)
|
|
55
|
-
Spacer()
|
|
56
|
-
Button("Confirm") { }
|
|
57
|
-
.buttonStyle(.borderedProminent)
|
|
58
|
-
}
|
|
59
|
-
.padding()
|
|
60
|
-
.background(.regularMaterial)
|
|
61
|
-
}
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
#### Adaptive Layouts
|
|
65
|
-
|
|
66
|
-
Use `horizontalSizeClass` to adapt between compact and regular widths:
|
|
67
|
-
|
|
68
|
-
```swift
|
|
69
|
-
@Environment(\.horizontalSizeClass) private var sizeClass
|
|
70
|
-
|
|
71
|
-
private var columns: [GridItem] {
|
|
72
|
-
switch sizeClass {
|
|
73
|
-
case .compact:
|
|
74
|
-
[GridItem(.flexible())]
|
|
75
|
-
case .regular:
|
|
76
|
-
[GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())]
|
|
77
|
-
default:
|
|
78
|
-
[GridItem(.flexible())]
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### Typography
|
|
84
|
-
|
|
85
|
-
#### System Font Styles
|
|
86
|
-
|
|
87
|
-
Use system font styles for automatic Dynamic Type support:
|
|
88
|
-
|
|
89
|
-
| Style | Size | Weight | Usage |
|
|
90
|
-
|-------|------|--------|-------|
|
|
91
|
-
| `.largeTitle` | 34pt | Bold | Screen titles |
|
|
92
|
-
| `.title` | 28pt | Semibold | Section headers |
|
|
93
|
-
| `.title2` | 22pt | Semibold | Sub-section headers |
|
|
94
|
-
| `.title3` | 20pt | Semibold | Group headers |
|
|
95
|
-
| `.headline` | 17pt | Semibold | Row titles |
|
|
96
|
-
| `.body` | 17pt | Regular | Primary content |
|
|
97
|
-
| `.callout` | 16pt | Regular | Secondary content |
|
|
98
|
-
| `.subheadline` | 15pt | Regular | Supporting text |
|
|
99
|
-
| `.footnote` | 13pt | Regular | Tertiary info |
|
|
100
|
-
| `.caption` | 12pt | Regular | Labels |
|
|
101
|
-
| `.caption2` | 11pt | Regular | Small labels |
|
|
102
|
-
|
|
103
|
-
#### Custom Font with Dynamic Type
|
|
104
|
-
|
|
105
|
-
```swift
|
|
106
|
-
extension Font {
|
|
107
|
-
static func customBody(_ name: String) -> Font {
|
|
108
|
-
.custom(name, size: 17, relativeTo: .body)
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
### Color System
|
|
114
|
-
|
|
115
|
-
#### Semantic Colors
|
|
116
|
-
|
|
117
|
-
Use semantic colors for automatic light/dark mode support:
|
|
118
|
-
|
|
119
|
-
```swift
|
|
120
|
-
// Labels
|
|
121
|
-
Color.primary // Primary text
|
|
122
|
-
Color.secondary // Secondary text
|
|
123
|
-
Color(uiColor: .tertiaryLabel)
|
|
124
|
-
|
|
125
|
-
// Backgrounds
|
|
126
|
-
Color(uiColor: .systemBackground)
|
|
127
|
-
Color(uiColor: .secondarySystemBackground)
|
|
128
|
-
Color(uiColor: .systemGroupedBackground)
|
|
129
|
-
|
|
130
|
-
// Fills and Separators
|
|
131
|
-
Color(uiColor: .systemFill)
|
|
132
|
-
Color(uiColor: .separator)
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
#### Tint Colors
|
|
136
|
-
|
|
137
|
-
```swift
|
|
138
|
-
// Apply app-wide tint
|
|
139
|
-
ContentView()
|
|
140
|
-
.tint(.blue)
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
Use `Color.accentColor` for interactive elements and `Color.red` for destructive actions.
|
|
144
|
-
|
|
145
|
-
### Navigation Patterns
|
|
146
|
-
|
|
147
|
-
#### Hierarchical (NavigationSplitView)
|
|
148
|
-
|
|
149
|
-
Use for iPad/macOS multi-column layouts:
|
|
150
|
-
|
|
151
|
-
```swift
|
|
152
|
-
NavigationSplitView {
|
|
153
|
-
List(items, selection: $selectedItem) { item in
|
|
154
|
-
NavigationLink(value: item) { ItemRow(item: item) }
|
|
155
|
-
}
|
|
156
|
-
.navigationTitle("Items")
|
|
157
|
-
} detail: {
|
|
158
|
-
if let item = selectedItem {
|
|
159
|
-
ItemDetailView(item: item)
|
|
160
|
-
} else {
|
|
161
|
-
ContentUnavailableView("Select an Item", systemImage: "sidebar.leading")
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
#### Tab-Based
|
|
167
|
-
|
|
168
|
-
Use `TabView` with a `NavigationStack` per tab. See the `swiftui-navigation` skill for full tab patterns.
|
|
169
|
-
|
|
170
|
-
#### Toolbar
|
|
171
|
-
|
|
172
|
-
```swift
|
|
173
|
-
.toolbar {
|
|
174
|
-
ToolbarItem(placement: .topBarLeading) { EditButton() }
|
|
175
|
-
ToolbarItemGroup(placement: .topBarTrailing) {
|
|
176
|
-
Button("Filter", systemImage: "line.3.horizontal.decrease.circle") { }
|
|
177
|
-
Button("Add", systemImage: "plus") { }
|
|
178
|
-
}
|
|
179
|
-
ToolbarItemGroup(placement: .bottomBar) {
|
|
180
|
-
Button("Archive", systemImage: "archivebox") { }
|
|
181
|
-
Spacer()
|
|
182
|
-
Text("\(itemCount) items").font(.footnote).foregroundStyle(.secondary)
|
|
183
|
-
Spacer()
|
|
184
|
-
Button("Share", systemImage: "square.and.arrow.up") { }
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
#### Search Integration
|
|
190
|
-
|
|
191
|
-
```swift
|
|
192
|
-
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
|
|
193
|
-
.searchScopes($searchScope) {
|
|
194
|
-
ForEach(SearchScope.allCases, id: \.self) { scope in
|
|
195
|
-
Text(scope.rawValue.capitalized).tag(scope)
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
### Feedback
|
|
201
|
-
|
|
202
|
-
#### Haptic Feedback
|
|
203
|
-
|
|
204
|
-
```swift
|
|
205
|
-
// Impact
|
|
206
|
-
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
|
207
|
-
|
|
208
|
-
// Notification
|
|
209
|
-
UINotificationFeedbackGenerator().notificationOccurred(.success)
|
|
210
|
-
|
|
211
|
-
// Selection
|
|
212
|
-
UISelectionFeedbackGenerator().selectionChanged()
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
See the Haptics section below for structured patterns.
|
|
216
|
-
|
|
217
|
-
### Accessibility
|
|
218
|
-
|
|
219
|
-
#### VoiceOver Support
|
|
220
|
-
|
|
221
|
-
```swift
|
|
222
|
-
VStack(alignment: .leading, spacing: 8) {
|
|
223
|
-
Text(item.title).font(.headline)
|
|
224
|
-
Text(item.subtitle).font(.subheadline).foregroundStyle(.secondary)
|
|
225
|
-
HStack {
|
|
226
|
-
Image(systemName: "star.fill")
|
|
227
|
-
Text("\(item.rating, specifier: "%.1f")")
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
.accessibilityElement(children: .combine)
|
|
231
|
-
.accessibilityLabel("\(item.title), \(item.subtitle)")
|
|
232
|
-
.accessibilityValue("Rating: \(item.rating) stars")
|
|
233
|
-
.accessibilityHint("Double tap to view details")
|
|
234
|
-
.accessibilityAddTraits(.isButton)
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
#### Dynamic Type Support
|
|
238
|
-
|
|
239
|
-
Adapt layout for accessibility sizes:
|
|
240
|
-
|
|
241
|
-
```swift
|
|
242
|
-
@Environment(\.dynamicTypeSize) private var dynamicTypeSize
|
|
243
|
-
|
|
244
|
-
var body: some View {
|
|
245
|
-
if dynamicTypeSize.isAccessibilitySize {
|
|
246
|
-
VStack(alignment: .leading, spacing: 12) {
|
|
247
|
-
leadingContent
|
|
248
|
-
trailingContent
|
|
249
|
-
}
|
|
250
|
-
} else {
|
|
251
|
-
HStack {
|
|
252
|
-
leadingContent
|
|
253
|
-
Spacer()
|
|
254
|
-
trailingContent
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
### Error and Empty States
|
|
261
|
-
|
|
262
|
-
Use `ContentUnavailableView` for both:
|
|
263
|
-
|
|
264
|
-
```swift
|
|
265
|
-
// Error state
|
|
266
|
-
ContentUnavailableView {
|
|
267
|
-
Label("Unable to Load", systemImage: "exclamationmark.triangle")
|
|
268
|
-
} description: {
|
|
269
|
-
Text(error.localizedDescription)
|
|
270
|
-
} actions: {
|
|
271
|
-
Button("Try Again") { Task { await retry() } }
|
|
272
|
-
.buttonStyle(.borderedProminent)
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Empty state
|
|
276
|
-
ContentUnavailableView {
|
|
277
|
-
Label("No Photos", systemImage: "camera")
|
|
278
|
-
} description: {
|
|
279
|
-
Text("Take your first photo to get started.")
|
|
280
|
-
} actions: {
|
|
281
|
-
Button("Take Photo") { showCamera = true }
|
|
282
|
-
.buttonStyle(.borderedProminent)
|
|
283
|
-
}
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
## Theming and Dynamic Type
|
|
287
|
-
|
|
288
|
-
### Intent
|
|
289
|
-
|
|
290
|
-
Provide a clean, scalable theming approach that keeps view code semantic and consistent.
|
|
291
|
-
|
|
292
|
-
### Core patterns
|
|
293
|
-
|
|
294
|
-
- Use a single `Theme` object as the source of truth (colors, fonts, spacing).
|
|
295
|
-
- Inject theme at the app root and read it via `@Environment(Theme.self)` in views.
|
|
296
|
-
- Prefer semantic colors (`primaryBackground`, `secondaryBackground`, `label`, `tint`) instead of raw colors.
|
|
297
|
-
- Keep user-facing theme controls in a dedicated settings screen.
|
|
298
|
-
- Apply Dynamic Type scaling through custom fonts or `.font(.scaled...)`.
|
|
299
|
-
|
|
300
|
-
### Example: Theme object
|
|
301
|
-
|
|
302
|
-
```swift
|
|
303
|
-
@MainActor
|
|
304
|
-
@Observable
|
|
305
|
-
final class Theme {
|
|
306
|
-
var tintColor: Color = .blue
|
|
307
|
-
var primaryBackground: Color = .white
|
|
308
|
-
var secondaryBackground: Color = .gray.opacity(0.1)
|
|
309
|
-
var labelColor: Color = .primary
|
|
310
|
-
var fontSizeScale: Double = 1.0
|
|
311
|
-
}
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
### Example: inject at app root
|
|
315
|
-
|
|
316
|
-
```swift
|
|
317
|
-
@main
|
|
318
|
-
struct MyApp: App {
|
|
319
|
-
@State private var theme = Theme()
|
|
320
|
-
|
|
321
|
-
var body: some Scene {
|
|
322
|
-
WindowGroup {
|
|
323
|
-
AppView()
|
|
324
|
-
.environment(theme)
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
### Example: view usage
|
|
331
|
-
|
|
332
|
-
```swift
|
|
333
|
-
struct ProfileView: View {
|
|
334
|
-
@Environment(Theme.self) private var theme
|
|
335
|
-
|
|
336
|
-
var body: some View {
|
|
337
|
-
VStack {
|
|
338
|
-
Text("Profile")
|
|
339
|
-
.foregroundStyle(theme.labelColor)
|
|
340
|
-
}
|
|
341
|
-
.background(theme.primaryBackground)
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
### Design choices to keep
|
|
347
|
-
|
|
348
|
-
- Keep theme values semantic and minimal; avoid duplicating system colors.
|
|
349
|
-
- Store user-selected theme values in persistent storage if needed.
|
|
350
|
-
- Ensure contrast between text and backgrounds.
|
|
351
|
-
|
|
352
|
-
### Pitfalls
|
|
353
|
-
|
|
354
|
-
- Avoid sprinkling raw `Color` values in views; it breaks consistency.
|
|
355
|
-
- Do not tie theme to a single view’s local state.
|
|
356
|
-
- Avoid using `@Environment(\\.colorScheme)` as the only theme control; it should complement your theme.
|
|
357
|
-
|
|
358
|
-
## Haptics
|
|
359
|
-
|
|
360
|
-
### Intent
|
|
361
|
-
|
|
362
|
-
Use haptics sparingly to reinforce user actions (tab selection, refresh, success/error) and respect user preferences.
|
|
363
|
-
|
|
364
|
-
### Core patterns
|
|
365
|
-
|
|
366
|
-
- Centralize haptic triggers in a `HapticManager` or similar utility.
|
|
367
|
-
- Gate haptics behind user preferences and hardware support.
|
|
368
|
-
- Use distinct types for different UX moments (selection vs. notification vs. refresh).
|
|
369
|
-
|
|
370
|
-
### Example: simple haptic manager
|
|
371
|
-
|
|
372
|
-
```swift
|
|
373
|
-
@MainActor
|
|
374
|
-
final class HapticManager {
|
|
375
|
-
static let shared = HapticManager()
|
|
376
|
-
|
|
377
|
-
enum HapticType {
|
|
378
|
-
case buttonPress
|
|
379
|
-
case tabSelection
|
|
380
|
-
case dataRefresh(intensity: CGFloat)
|
|
381
|
-
case notification(UINotificationFeedbackGenerator.FeedbackType)
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
private let selectionGenerator = UISelectionFeedbackGenerator()
|
|
385
|
-
private let impactGenerator = UIImpactFeedbackGenerator(style: .heavy)
|
|
386
|
-
private let notificationGenerator = UINotificationFeedbackGenerator()
|
|
387
|
-
|
|
388
|
-
private init() { selectionGenerator.prepare() }
|
|
389
|
-
|
|
390
|
-
func fire(_ type: HapticType, isEnabled: Bool) {
|
|
391
|
-
guard isEnabled else { return }
|
|
392
|
-
switch type {
|
|
393
|
-
case .buttonPress:
|
|
394
|
-
impactGenerator.impactOccurred()
|
|
395
|
-
case .tabSelection:
|
|
396
|
-
selectionGenerator.selectionChanged()
|
|
397
|
-
case let .dataRefresh(intensity):
|
|
398
|
-
impactGenerator.impactOccurred(intensity: intensity)
|
|
399
|
-
case let .notification(style):
|
|
400
|
-
notificationGenerator.notificationOccurred(style)
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
### Example: usage
|
|
407
|
-
|
|
408
|
-
```swift
|
|
409
|
-
Button("Save") {
|
|
410
|
-
HapticManager.shared.fire(.notification(.success), isEnabled: preferences.hapticsEnabled)
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
TabView(selection: $selectedTab) { /* tabs */ }
|
|
414
|
-
.onChange(of: selectedTab) { _, _ in
|
|
415
|
-
HapticManager.shared.fire(.tabSelection, isEnabled: preferences.hapticTabSelectionEnabled)
|
|
416
|
-
}
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
### Design choices to keep
|
|
420
|
-
|
|
421
|
-
- Haptics should be subtle and not fire on every tiny interaction.
|
|
422
|
-
- Respect user preferences (toggle to disable).
|
|
423
|
-
- Keep haptic triggers close to the user action, not deep in data layers.
|
|
424
|
-
|
|
425
|
-
### Pitfalls
|
|
426
|
-
|
|
427
|
-
- Avoid firing multiple haptics in quick succession.
|
|
428
|
-
- Do not assume haptics are available; check support.
|
|
429
|
-
|
|
430
|
-
### Core Haptics (CHHapticEngine)
|
|
431
|
-
|
|
432
|
-
For advanced haptic patterns beyond the simple feedback generators, use Core Haptics. It provides precise control over haptic intensity, sharpness, and timing with support for audio-haptic synchronization.
|
|
433
|
-
|
|
434
|
-
> **Docs:** [CHHapticEngine](https://sosumi.ai/documentation/corehaptics/chhapticengine) · [Preparing your app to play haptics](https://sosumi.ai/documentation/corehaptics/preparing-your-app-to-play-haptics)
|
|
435
|
-
|
|
436
|
-
#### Capabilities check
|
|
437
|
-
|
|
438
|
-
Always verify hardware support before creating an engine:
|
|
439
|
-
|
|
440
|
-
```swift
|
|
441
|
-
import CoreHaptics
|
|
442
|
-
|
|
443
|
-
let supportsHaptics = CHHapticEngine.capabilitiesForHardware().supportsHaptics
|
|
444
|
-
let supportsAudio = CHHapticEngine.capabilitiesForHardware().supportsAudio
|
|
445
|
-
```
|
|
446
|
-
|
|
447
|
-
#### Engine setup and lifecycle
|
|
448
|
-
|
|
449
|
-
```swift
|
|
450
|
-
@MainActor
|
|
451
|
-
final class CoreHapticManager {
|
|
452
|
-
private var engine: CHHapticEngine?
|
|
453
|
-
|
|
454
|
-
func prepareEngine() throws {
|
|
455
|
-
guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { return }
|
|
456
|
-
|
|
457
|
-
engine = try CHHapticEngine()
|
|
458
|
-
|
|
459
|
-
// Called when the engine stops due to external cause (audio session interruption, app backgrounding)
|
|
460
|
-
engine?.stoppedHandler = { reason in
|
|
461
|
-
print("Haptic engine stopped: \(reason)")
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// Called after the engine is reset (e.g., after audio session interruption ends)
|
|
465
|
-
engine?.resetHandler = { [weak self] in
|
|
466
|
-
do {
|
|
467
|
-
try self?.engine?.start()
|
|
468
|
-
} catch {
|
|
469
|
-
print("Failed to restart engine: \(error)")
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
try engine?.start()
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
func stopEngine() {
|
|
477
|
-
engine?.stop()
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
Key lifecycle rules:
|
|
483
|
-
- Call `engine.start()` before playing any patterns.
|
|
484
|
-
- Handle `stoppedHandler` — the system can stop the engine when your app moves to the background or during audio interruptions.
|
|
485
|
-
- Handle `resetHandler` — restart the engine when the system resets it.
|
|
486
|
-
- Call `engine.stop()` when haptics are no longer needed to save battery.
|
|
487
|
-
|
|
488
|
-
#### CHHapticPattern and CHHapticEvent
|
|
489
|
-
|
|
490
|
-
Build patterns from individual haptic and audio events:
|
|
491
|
-
|
|
492
|
-
```swift
|
|
493
|
-
func playTransientTap() throws {
|
|
494
|
-
let sharpness = CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.8)
|
|
495
|
-
let intensity = CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0)
|
|
496
|
-
|
|
497
|
-
// Transient: short, single-tap feel
|
|
498
|
-
let event = CHHapticEvent(
|
|
499
|
-
eventType: .hapticTransient,
|
|
500
|
-
parameters: [intensity, sharpness],
|
|
501
|
-
relativeTime: 0
|
|
502
|
-
)
|
|
503
|
-
|
|
504
|
-
let pattern = try CHHapticPattern(events: [event], parameters: [])
|
|
505
|
-
let player = try engine?.makePlayer(with: pattern)
|
|
506
|
-
try player?.start(atTime: CHHapticTimeImmediate)
|
|
507
|
-
}
|
|
508
|
-
```
|
|
509
|
-
|
|
510
|
-
**Event types:**
|
|
511
|
-
|
|
512
|
-
| Type | Description |
|
|
513
|
-
|------|-------------|
|
|
514
|
-
| `.hapticTransient` | Brief, tap-like impulse |
|
|
515
|
-
| `.hapticContinuous` | Sustained vibration over a `duration` |
|
|
516
|
-
| `.audioContinuous` | Sustained audio tone |
|
|
517
|
-
| `.audioCustom` | Play a custom audio resource |
|
|
518
|
-
|
|
519
|
-
**Common parameters:** `.hapticIntensity` (0–1), `.hapticSharpness` (0–1), `.attackTime`, `.decayTime`, `.releaseTime`.
|
|
520
|
-
|
|
521
|
-
#### Playing patterns with CHHapticPatternPlayer
|
|
522
|
-
|
|
523
|
-
```swift
|
|
524
|
-
func playContinuousBuzz() throws {
|
|
525
|
-
let intensity = CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.6)
|
|
526
|
-
let sharpness = CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.3)
|
|
527
|
-
|
|
528
|
-
let event = CHHapticEvent(
|
|
529
|
-
eventType: .hapticContinuous,
|
|
530
|
-
parameters: [intensity, sharpness],
|
|
531
|
-
relativeTime: 0,
|
|
532
|
-
duration: 0.5
|
|
533
|
-
)
|
|
534
|
-
|
|
535
|
-
let pattern = try CHHapticPattern(events: [event], parameters: [])
|
|
536
|
-
let player = try engine?.makePlayer(with: pattern)
|
|
537
|
-
try player?.start(atTime: CHHapticTimeImmediate)
|
|
538
|
-
}
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
For looping, seeking, and pausing, use `CHHapticAdvancedPatternPlayer` via `engine.makeAdvancedPlayer(with:)`.
|
|
542
|
-
|
|
543
|
-
#### Haptic parameter curves (CHHapticParameterCurve)
|
|
544
|
-
|
|
545
|
-
Smoothly vary parameters over time within a pattern:
|
|
546
|
-
|
|
547
|
-
```swift
|
|
548
|
-
func playRampingPattern() throws {
|
|
549
|
-
let event = CHHapticEvent(
|
|
550
|
-
eventType: .hapticContinuous,
|
|
551
|
-
parameters: [
|
|
552
|
-
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.2),
|
|
553
|
-
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.1)
|
|
554
|
-
],
|
|
555
|
-
relativeTime: 0,
|
|
556
|
-
duration: 1.0
|
|
557
|
-
)
|
|
558
|
-
|
|
559
|
-
// Ramp intensity from 0.2 → 1.0 over 1 second
|
|
560
|
-
let curve = CHHapticParameterCurve(
|
|
561
|
-
parameterID: .hapticIntensityControl,
|
|
562
|
-
controlPoints: [
|
|
563
|
-
.init(relativeTime: 0, value: 0.2),
|
|
564
|
-
.init(relativeTime: 0.5, value: 0.7),
|
|
565
|
-
.init(relativeTime: 1.0, value: 1.0)
|
|
566
|
-
],
|
|
567
|
-
relativeTime: 0
|
|
568
|
-
)
|
|
569
|
-
|
|
570
|
-
let pattern = try CHHapticPattern(events: [event], parameterCurves: [curve])
|
|
571
|
-
let player = try engine?.makePlayer(with: pattern)
|
|
572
|
-
try player?.start(atTime: CHHapticTimeImmediate)
|
|
573
|
-
}
|
|
574
|
-
```
|
|
575
|
-
|
|
576
|
-
#### Audio-haptic synchronization (AHAP files)
|
|
577
|
-
|
|
578
|
-
AHAP (Apple Haptic and Audio Pattern) files define haptic patterns in JSON for easy authoring and design iteration. Load them directly:
|
|
579
|
-
|
|
580
|
-
```swift
|
|
581
|
-
func playAHAPFile() throws {
|
|
582
|
-
guard let url = Bundle.main.url(forResource: "success", withExtension: "ahap") else { return }
|
|
583
|
-
try engine?.playPattern(from: url)
|
|
584
|
-
}
|
|
585
|
-
```
|
|
586
|
-
|
|
587
|
-
AHAP files support the same events, parameters, and parameter curves as the programmatic API. Use the **Core Haptics** design tools in Xcode to preview patterns.
|
|
588
|
-
|
|
589
|
-
> **Docs:** [Representing haptic patterns in AHAP files](https://sosumi.ai/documentation/corehaptics/representing-haptic-patterns-in-ahap-files)
|
|
590
|
-
|
|
591
|
-
## Matched Transitions
|
|
592
|
-
|
|
593
|
-
### Intent
|
|
594
|
-
|
|
595
|
-
Use matched transitions to create smooth continuity between a source view (thumbnail, avatar) and a destination view (sheet, detail, viewer).
|
|
596
|
-
|
|
597
|
-
### Core patterns
|
|
598
|
-
|
|
599
|
-
- Use a shared `Namespace` and a stable ID for the source.
|
|
600
|
-
- Use `matchedTransitionSource` + `navigationTransition(.zoom(...))` on iOS 26+.
|
|
601
|
-
- Use `matchedGeometryEffect` for in-place transitions within a view hierarchy.
|
|
602
|
-
- Keep IDs stable across view updates (avoid random UUIDs).
|
|
603
|
-
|
|
604
|
-
### Example: media preview to full-screen viewer (iOS 26+)
|
|
605
|
-
|
|
606
|
-
```swift
|
|
607
|
-
struct MediaPreview: View {
|
|
608
|
-
@Namespace private var namespace
|
|
609
|
-
@State private var selected: MediaAttachment?
|
|
610
|
-
|
|
611
|
-
var body: some View {
|
|
612
|
-
ThumbnailView()
|
|
613
|
-
.matchedTransitionSource(id: selected?.id ?? "", in: namespace)
|
|
614
|
-
.sheet(item: $selected) { item in
|
|
615
|
-
MediaViewer(item: item)
|
|
616
|
-
.navigationTransition(.zoom(sourceID: item.id, in: namespace))
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
```
|
|
621
|
-
|
|
622
|
-
### Example: matched geometry within a view
|
|
623
|
-
|
|
624
|
-
```swift
|
|
625
|
-
struct ToggleBadge: View {
|
|
626
|
-
@Namespace private var space
|
|
627
|
-
@State private var isOn = false
|
|
628
|
-
|
|
629
|
-
var body: some View {
|
|
630
|
-
Button {
|
|
631
|
-
withAnimation(.spring) { isOn.toggle() }
|
|
632
|
-
} label: {
|
|
633
|
-
Image(systemName: isOn ? "eye" : "eye.slash")
|
|
634
|
-
.matchedGeometryEffect(id: "icon", in: space)
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
```
|
|
639
|
-
|
|
640
|
-
### Design choices to keep
|
|
641
|
-
|
|
642
|
-
- Prefer `matchedTransitionSource` for cross-screen transitions.
|
|
643
|
-
- Keep source and destination sizes reasonable to avoid jarring scale changes.
|
|
644
|
-
- Use `withAnimation` for state-driven transitions.
|
|
645
|
-
|
|
646
|
-
### Pitfalls
|
|
647
|
-
|
|
648
|
-
- Don’t use unstable IDs; it breaks the transition.
|
|
649
|
-
- Avoid mismatched shapes (e.g., square to circle) unless the design expects it.
|
|
650
|
-
|
|
651
|
-
## Loading and Placeholders
|
|
652
|
-
|
|
653
|
-
Use this when a view needs a consistent loading state (skeletons, redaction, empty state) without blocking interaction.
|
|
654
|
-
|
|
655
|
-
### Patterns to prefer
|
|
656
|
-
|
|
657
|
-
- **Redacted placeholders** for list/detail content to preserve layout while loading.
|
|
658
|
-
- **ContentUnavailableView** for empty or error states after loading completes.
|
|
659
|
-
- **ProgressView** only for short, global operations (use sparingly in content-heavy screens).
|
|
660
|
-
|
|
661
|
-
### Recommended approach
|
|
662
|
-
|
|
663
|
-
1. Keep the real layout, render placeholder data, then apply `.redacted(reason: .placeholder)`.
|
|
664
|
-
2. For lists, show a fixed number of placeholder rows (avoid infinite spinners).
|
|
665
|
-
3. Switch to `ContentUnavailableView` when load finishes but data is empty.
|
|
666
|
-
|
|
667
|
-
### Pitfalls
|
|
668
|
-
|
|
669
|
-
- Don’t animate layout shifts during redaction; keep frames stable.
|
|
670
|
-
- Avoid nesting multiple spinners; use one loading indicator per section.
|
|
671
|
-
- Keep placeholder count small (3–6) to reduce jank on low-end devices.
|
|
672
|
-
|
|
673
|
-
### Minimal usage
|
|
674
|
-
|
|
675
|
-
```swift
|
|
676
|
-
VStack {
|
|
677
|
-
if isLoading {
|
|
678
|
-
ForEach(0..<3, id: \.self) { _ in
|
|
679
|
-
RowView(model: .placeholder())
|
|
680
|
-
}
|
|
681
|
-
.redacted(reason: .placeholder)
|
|
682
|
-
} else if items.isEmpty {
|
|
683
|
-
ContentUnavailableView("No items", systemImage: "tray")
|
|
684
|
-
} else {
|
|
685
|
-
ForEach(items) { item in RowView(model: item) }
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
```
|
|
689
|
-
|
|
690
|
-
## Focus Handling
|
|
691
|
-
|
|
692
|
-
### Intent
|
|
693
|
-
|
|
694
|
-
Use `@FocusState` to control keyboard focus, chain fields, and coordinate focus across complex forms.
|
|
695
|
-
|
|
696
|
-
### Core patterns
|
|
697
|
-
|
|
698
|
-
- Use an enum to represent focusable fields.
|
|
699
|
-
- Set initial focus in `onAppear`.
|
|
700
|
-
- Use `.onSubmit` to move focus to the next field.
|
|
701
|
-
- For dynamic lists of fields, use an enum with associated values (e.g., `.option(Int)`).
|
|
702
|
-
|
|
703
|
-
### Example: single field focus
|
|
704
|
-
|
|
705
|
-
```swift
|
|
706
|
-
struct AddServerView: View {
|
|
707
|
-
@State private var server = ""
|
|
708
|
-
@FocusState private var isServerFieldFocused: Bool
|
|
709
|
-
|
|
710
|
-
var body: some View {
|
|
711
|
-
Form {
|
|
712
|
-
TextField("Server", text: $server)
|
|
713
|
-
.focused($isServerFieldFocused)
|
|
714
|
-
}
|
|
715
|
-
.onAppear { isServerFieldFocused = true }
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
```
|
|
719
|
-
|
|
720
|
-
### Example: chained focus with enum
|
|
721
|
-
|
|
722
|
-
```swift
|
|
723
|
-
struct EditTagView: View {
|
|
724
|
-
enum FocusField { case title, symbol, newTag }
|
|
725
|
-
@FocusState private var focusedField: FocusField?
|
|
726
|
-
|
|
727
|
-
var body: some View {
|
|
728
|
-
Form {
|
|
729
|
-
TextField("Title", text: $title)
|
|
730
|
-
.focused($focusedField, equals: .title)
|
|
731
|
-
.onSubmit { focusedField = .symbol }
|
|
732
|
-
|
|
733
|
-
TextField("Symbol", text: $symbol)
|
|
734
|
-
.focused($focusedField, equals: .symbol)
|
|
735
|
-
.onSubmit { focusedField = .newTag }
|
|
736
|
-
}
|
|
737
|
-
.onAppear { focusedField = .title }
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
```
|
|
741
|
-
|
|
742
|
-
### Example: dynamic focus for variable fields
|
|
743
|
-
|
|
744
|
-
```swift
|
|
745
|
-
struct PollView: View {
|
|
746
|
-
enum FocusField: Hashable { case option(Int) }
|
|
747
|
-
@FocusState private var focused: FocusField?
|
|
748
|
-
@State private var options: [String] = ["", ""]
|
|
749
|
-
@State private var currentIndex = 0
|
|
750
|
-
|
|
751
|
-
var body: some View {
|
|
752
|
-
ForEach(options.indices, id: \.self) { index in
|
|
753
|
-
TextField("Option \(index + 1)", text: $options[index])
|
|
754
|
-
.focused($focused, equals: .option(index))
|
|
755
|
-
.onSubmit { addOption(at: index) }
|
|
756
|
-
}
|
|
757
|
-
.onAppear { focused = .option(0) }
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
private func addOption(at index: Int) {
|
|
761
|
-
options.append("")
|
|
762
|
-
currentIndex = index + 1
|
|
763
|
-
Task { @MainActor in
|
|
764
|
-
try? await Task.sleep(for: .milliseconds(10))
|
|
765
|
-
focused = .option(currentIndex)
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
```
|
|
770
|
-
|
|
771
|
-
### Design choices to keep
|
|
772
|
-
|
|
773
|
-
- Keep focus state local to the view that owns the fields.
|
|
774
|
-
- Use focus changes to drive UX (validation messages, helper UI).
|
|
775
|
-
- Pair with `.scrollDismissesKeyboard(...)` when using ScrollView/Form.
|
|
776
|
-
|
|
777
|
-
### Pitfalls
|
|
778
|
-
|
|
779
|
-
- Don’t store focus state in shared objects; it is view-local.
|
|
780
|
-
- Avoid aggressive focus changes during animation; delay if needed.
|
|
1
|
+
# Design Polish
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
- [HIG Alignment](#hig-alignment)
|
|
5
|
+
- [Theming and Dynamic Type](#theming-and-dynamic-type)
|
|
6
|
+
- [Haptics](#haptics)
|
|
7
|
+
- [Matched Transitions](#matched-transitions)
|
|
8
|
+
- [Loading and Placeholders](#loading-and-placeholders)
|
|
9
|
+
- [Focus Handling](#focus-handling)
|
|
10
|
+
|
|
11
|
+
## HIG Alignment
|
|
12
|
+
|
|
13
|
+
iOS Human Interface Guidelines patterns for layout, typography, color, accessibility, and feedback in SwiftUI.
|
|
14
|
+
|
|
15
|
+
### Contents
|
|
16
|
+
|
|
17
|
+
- [Layout and Spacing](#layout-and-spacing)
|
|
18
|
+
- [Typography](#typography)
|
|
19
|
+
- [Color System](#color-system)
|
|
20
|
+
- [Navigation Patterns](#navigation-patterns)
|
|
21
|
+
- [Feedback](#feedback)
|
|
22
|
+
- [Accessibility](#accessibility)
|
|
23
|
+
- [Error and Empty States](#error-and-empty-states)
|
|
24
|
+
|
|
25
|
+
### Layout and Spacing
|
|
26
|
+
|
|
27
|
+
#### Standard Margins
|
|
28
|
+
|
|
29
|
+
```swift
|
|
30
|
+
private let standardMargin: CGFloat = 16
|
|
31
|
+
private let compactMargin: CGFloat = 8
|
|
32
|
+
private let largeMargin: CGFloat = 24
|
|
33
|
+
|
|
34
|
+
extension EdgeInsets {
|
|
35
|
+
static let standard = EdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16)
|
|
36
|
+
static let listRow = EdgeInsets(top: 12, leading: 16, bottom: 12, trailing: 16)
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
#### Safe Area Handling
|
|
41
|
+
|
|
42
|
+
```swift
|
|
43
|
+
ScrollView {
|
|
44
|
+
LazyVStack(spacing: 16) {
|
|
45
|
+
ForEach(items) { item in
|
|
46
|
+
ItemRow(item: item)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
.padding(.horizontal)
|
|
50
|
+
}
|
|
51
|
+
.safeAreaInset(edge: .bottom) {
|
|
52
|
+
HStack {
|
|
53
|
+
Button("Cancel") { }
|
|
54
|
+
.buttonStyle(.bordered)
|
|
55
|
+
Spacer()
|
|
56
|
+
Button("Confirm") { }
|
|
57
|
+
.buttonStyle(.borderedProminent)
|
|
58
|
+
}
|
|
59
|
+
.padding()
|
|
60
|
+
.background(.regularMaterial)
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
#### Adaptive Layouts
|
|
65
|
+
|
|
66
|
+
Use `horizontalSizeClass` to adapt between compact and regular widths:
|
|
67
|
+
|
|
68
|
+
```swift
|
|
69
|
+
@Environment(\.horizontalSizeClass) private var sizeClass
|
|
70
|
+
|
|
71
|
+
private var columns: [GridItem] {
|
|
72
|
+
switch sizeClass {
|
|
73
|
+
case .compact:
|
|
74
|
+
[GridItem(.flexible())]
|
|
75
|
+
case .regular:
|
|
76
|
+
[GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())]
|
|
77
|
+
default:
|
|
78
|
+
[GridItem(.flexible())]
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Typography
|
|
84
|
+
|
|
85
|
+
#### System Font Styles
|
|
86
|
+
|
|
87
|
+
Use system font styles for automatic Dynamic Type support:
|
|
88
|
+
|
|
89
|
+
| Style | Size | Weight | Usage |
|
|
90
|
+
|-------|------|--------|-------|
|
|
91
|
+
| `.largeTitle` | 34pt | Bold | Screen titles |
|
|
92
|
+
| `.title` | 28pt | Semibold | Section headers |
|
|
93
|
+
| `.title2` | 22pt | Semibold | Sub-section headers |
|
|
94
|
+
| `.title3` | 20pt | Semibold | Group headers |
|
|
95
|
+
| `.headline` | 17pt | Semibold | Row titles |
|
|
96
|
+
| `.body` | 17pt | Regular | Primary content |
|
|
97
|
+
| `.callout` | 16pt | Regular | Secondary content |
|
|
98
|
+
| `.subheadline` | 15pt | Regular | Supporting text |
|
|
99
|
+
| `.footnote` | 13pt | Regular | Tertiary info |
|
|
100
|
+
| `.caption` | 12pt | Regular | Labels |
|
|
101
|
+
| `.caption2` | 11pt | Regular | Small labels |
|
|
102
|
+
|
|
103
|
+
#### Custom Font with Dynamic Type
|
|
104
|
+
|
|
105
|
+
```swift
|
|
106
|
+
extension Font {
|
|
107
|
+
static func customBody(_ name: String) -> Font {
|
|
108
|
+
.custom(name, size: 17, relativeTo: .body)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Color System
|
|
114
|
+
|
|
115
|
+
#### Semantic Colors
|
|
116
|
+
|
|
117
|
+
Use semantic colors for automatic light/dark mode support:
|
|
118
|
+
|
|
119
|
+
```swift
|
|
120
|
+
// Labels
|
|
121
|
+
Color.primary // Primary text
|
|
122
|
+
Color.secondary // Secondary text
|
|
123
|
+
Color(uiColor: .tertiaryLabel)
|
|
124
|
+
|
|
125
|
+
// Backgrounds
|
|
126
|
+
Color(uiColor: .systemBackground)
|
|
127
|
+
Color(uiColor: .secondarySystemBackground)
|
|
128
|
+
Color(uiColor: .systemGroupedBackground)
|
|
129
|
+
|
|
130
|
+
// Fills and Separators
|
|
131
|
+
Color(uiColor: .systemFill)
|
|
132
|
+
Color(uiColor: .separator)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
#### Tint Colors
|
|
136
|
+
|
|
137
|
+
```swift
|
|
138
|
+
// Apply app-wide tint
|
|
139
|
+
ContentView()
|
|
140
|
+
.tint(.blue)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Use `Color.accentColor` for interactive elements and `Color.red` for destructive actions.
|
|
144
|
+
|
|
145
|
+
### Navigation Patterns
|
|
146
|
+
|
|
147
|
+
#### Hierarchical (NavigationSplitView)
|
|
148
|
+
|
|
149
|
+
Use for iPad/macOS multi-column layouts:
|
|
150
|
+
|
|
151
|
+
```swift
|
|
152
|
+
NavigationSplitView {
|
|
153
|
+
List(items, selection: $selectedItem) { item in
|
|
154
|
+
NavigationLink(value: item) { ItemRow(item: item) }
|
|
155
|
+
}
|
|
156
|
+
.navigationTitle("Items")
|
|
157
|
+
} detail: {
|
|
158
|
+
if let item = selectedItem {
|
|
159
|
+
ItemDetailView(item: item)
|
|
160
|
+
} else {
|
|
161
|
+
ContentUnavailableView("Select an Item", systemImage: "sidebar.leading")
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
#### Tab-Based
|
|
167
|
+
|
|
168
|
+
Use `TabView` with a `NavigationStack` per tab. See the `swiftui-navigation` skill for full tab patterns.
|
|
169
|
+
|
|
170
|
+
#### Toolbar
|
|
171
|
+
|
|
172
|
+
```swift
|
|
173
|
+
.toolbar {
|
|
174
|
+
ToolbarItem(placement: .topBarLeading) { EditButton() }
|
|
175
|
+
ToolbarItemGroup(placement: .topBarTrailing) {
|
|
176
|
+
Button("Filter", systemImage: "line.3.horizontal.decrease.circle") { }
|
|
177
|
+
Button("Add", systemImage: "plus") { }
|
|
178
|
+
}
|
|
179
|
+
ToolbarItemGroup(placement: .bottomBar) {
|
|
180
|
+
Button("Archive", systemImage: "archivebox") { }
|
|
181
|
+
Spacer()
|
|
182
|
+
Text("\(itemCount) items").font(.footnote).foregroundStyle(.secondary)
|
|
183
|
+
Spacer()
|
|
184
|
+
Button("Share", systemImage: "square.and.arrow.up") { }
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### Search Integration
|
|
190
|
+
|
|
191
|
+
```swift
|
|
192
|
+
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
|
|
193
|
+
.searchScopes($searchScope) {
|
|
194
|
+
ForEach(SearchScope.allCases, id: \.self) { scope in
|
|
195
|
+
Text(scope.rawValue.capitalized).tag(scope)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Feedback
|
|
201
|
+
|
|
202
|
+
#### Haptic Feedback
|
|
203
|
+
|
|
204
|
+
```swift
|
|
205
|
+
// Impact
|
|
206
|
+
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
|
207
|
+
|
|
208
|
+
// Notification
|
|
209
|
+
UINotificationFeedbackGenerator().notificationOccurred(.success)
|
|
210
|
+
|
|
211
|
+
// Selection
|
|
212
|
+
UISelectionFeedbackGenerator().selectionChanged()
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
See the Haptics section below for structured patterns.
|
|
216
|
+
|
|
217
|
+
### Accessibility
|
|
218
|
+
|
|
219
|
+
#### VoiceOver Support
|
|
220
|
+
|
|
221
|
+
```swift
|
|
222
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
223
|
+
Text(item.title).font(.headline)
|
|
224
|
+
Text(item.subtitle).font(.subheadline).foregroundStyle(.secondary)
|
|
225
|
+
HStack {
|
|
226
|
+
Image(systemName: "star.fill")
|
|
227
|
+
Text("\(item.rating, specifier: "%.1f")")
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
.accessibilityElement(children: .combine)
|
|
231
|
+
.accessibilityLabel("\(item.title), \(item.subtitle)")
|
|
232
|
+
.accessibilityValue("Rating: \(item.rating) stars")
|
|
233
|
+
.accessibilityHint("Double tap to view details")
|
|
234
|
+
.accessibilityAddTraits(.isButton)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### Dynamic Type Support
|
|
238
|
+
|
|
239
|
+
Adapt layout for accessibility sizes:
|
|
240
|
+
|
|
241
|
+
```swift
|
|
242
|
+
@Environment(\.dynamicTypeSize) private var dynamicTypeSize
|
|
243
|
+
|
|
244
|
+
var body: some View {
|
|
245
|
+
if dynamicTypeSize.isAccessibilitySize {
|
|
246
|
+
VStack(alignment: .leading, spacing: 12) {
|
|
247
|
+
leadingContent
|
|
248
|
+
trailingContent
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
HStack {
|
|
252
|
+
leadingContent
|
|
253
|
+
Spacer()
|
|
254
|
+
trailingContent
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Error and Empty States
|
|
261
|
+
|
|
262
|
+
Use `ContentUnavailableView` for both:
|
|
263
|
+
|
|
264
|
+
```swift
|
|
265
|
+
// Error state
|
|
266
|
+
ContentUnavailableView {
|
|
267
|
+
Label("Unable to Load", systemImage: "exclamationmark.triangle")
|
|
268
|
+
} description: {
|
|
269
|
+
Text(error.localizedDescription)
|
|
270
|
+
} actions: {
|
|
271
|
+
Button("Try Again") { Task { await retry() } }
|
|
272
|
+
.buttonStyle(.borderedProminent)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Empty state
|
|
276
|
+
ContentUnavailableView {
|
|
277
|
+
Label("No Photos", systemImage: "camera")
|
|
278
|
+
} description: {
|
|
279
|
+
Text("Take your first photo to get started.")
|
|
280
|
+
} actions: {
|
|
281
|
+
Button("Take Photo") { showCamera = true }
|
|
282
|
+
.buttonStyle(.borderedProminent)
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## Theming and Dynamic Type
|
|
287
|
+
|
|
288
|
+
### Intent
|
|
289
|
+
|
|
290
|
+
Provide a clean, scalable theming approach that keeps view code semantic and consistent.
|
|
291
|
+
|
|
292
|
+
### Core patterns
|
|
293
|
+
|
|
294
|
+
- Use a single `Theme` object as the source of truth (colors, fonts, spacing).
|
|
295
|
+
- Inject theme at the app root and read it via `@Environment(Theme.self)` in views.
|
|
296
|
+
- Prefer semantic colors (`primaryBackground`, `secondaryBackground`, `label`, `tint`) instead of raw colors.
|
|
297
|
+
- Keep user-facing theme controls in a dedicated settings screen.
|
|
298
|
+
- Apply Dynamic Type scaling through custom fonts or `.font(.scaled...)`.
|
|
299
|
+
|
|
300
|
+
### Example: Theme object
|
|
301
|
+
|
|
302
|
+
```swift
|
|
303
|
+
@MainActor
|
|
304
|
+
@Observable
|
|
305
|
+
final class Theme {
|
|
306
|
+
var tintColor: Color = .blue
|
|
307
|
+
var primaryBackground: Color = .white
|
|
308
|
+
var secondaryBackground: Color = .gray.opacity(0.1)
|
|
309
|
+
var labelColor: Color = .primary
|
|
310
|
+
var fontSizeScale: Double = 1.0
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Example: inject at app root
|
|
315
|
+
|
|
316
|
+
```swift
|
|
317
|
+
@main
|
|
318
|
+
struct MyApp: App {
|
|
319
|
+
@State private var theme = Theme()
|
|
320
|
+
|
|
321
|
+
var body: some Scene {
|
|
322
|
+
WindowGroup {
|
|
323
|
+
AppView()
|
|
324
|
+
.environment(theme)
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Example: view usage
|
|
331
|
+
|
|
332
|
+
```swift
|
|
333
|
+
struct ProfileView: View {
|
|
334
|
+
@Environment(Theme.self) private var theme
|
|
335
|
+
|
|
336
|
+
var body: some View {
|
|
337
|
+
VStack {
|
|
338
|
+
Text("Profile")
|
|
339
|
+
.foregroundStyle(theme.labelColor)
|
|
340
|
+
}
|
|
341
|
+
.background(theme.primaryBackground)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Design choices to keep
|
|
347
|
+
|
|
348
|
+
- Keep theme values semantic and minimal; avoid duplicating system colors.
|
|
349
|
+
- Store user-selected theme values in persistent storage if needed.
|
|
350
|
+
- Ensure contrast between text and backgrounds.
|
|
351
|
+
|
|
352
|
+
### Pitfalls
|
|
353
|
+
|
|
354
|
+
- Avoid sprinkling raw `Color` values in views; it breaks consistency.
|
|
355
|
+
- Do not tie theme to a single view’s local state.
|
|
356
|
+
- Avoid using `@Environment(\\.colorScheme)` as the only theme control; it should complement your theme.
|
|
357
|
+
|
|
358
|
+
## Haptics
|
|
359
|
+
|
|
360
|
+
### Intent
|
|
361
|
+
|
|
362
|
+
Use haptics sparingly to reinforce user actions (tab selection, refresh, success/error) and respect user preferences.
|
|
363
|
+
|
|
364
|
+
### Core patterns
|
|
365
|
+
|
|
366
|
+
- Centralize haptic triggers in a `HapticManager` or similar utility.
|
|
367
|
+
- Gate haptics behind user preferences and hardware support.
|
|
368
|
+
- Use distinct types for different UX moments (selection vs. notification vs. refresh).
|
|
369
|
+
|
|
370
|
+
### Example: simple haptic manager
|
|
371
|
+
|
|
372
|
+
```swift
|
|
373
|
+
@MainActor
|
|
374
|
+
final class HapticManager {
|
|
375
|
+
static let shared = HapticManager()
|
|
376
|
+
|
|
377
|
+
enum HapticType {
|
|
378
|
+
case buttonPress
|
|
379
|
+
case tabSelection
|
|
380
|
+
case dataRefresh(intensity: CGFloat)
|
|
381
|
+
case notification(UINotificationFeedbackGenerator.FeedbackType)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
private let selectionGenerator = UISelectionFeedbackGenerator()
|
|
385
|
+
private let impactGenerator = UIImpactFeedbackGenerator(style: .heavy)
|
|
386
|
+
private let notificationGenerator = UINotificationFeedbackGenerator()
|
|
387
|
+
|
|
388
|
+
private init() { selectionGenerator.prepare() }
|
|
389
|
+
|
|
390
|
+
func fire(_ type: HapticType, isEnabled: Bool) {
|
|
391
|
+
guard isEnabled else { return }
|
|
392
|
+
switch type {
|
|
393
|
+
case .buttonPress:
|
|
394
|
+
impactGenerator.impactOccurred()
|
|
395
|
+
case .tabSelection:
|
|
396
|
+
selectionGenerator.selectionChanged()
|
|
397
|
+
case let .dataRefresh(intensity):
|
|
398
|
+
impactGenerator.impactOccurred(intensity: intensity)
|
|
399
|
+
case let .notification(style):
|
|
400
|
+
notificationGenerator.notificationOccurred(style)
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Example: usage
|
|
407
|
+
|
|
408
|
+
```swift
|
|
409
|
+
Button("Save") {
|
|
410
|
+
HapticManager.shared.fire(.notification(.success), isEnabled: preferences.hapticsEnabled)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
TabView(selection: $selectedTab) { /* tabs */ }
|
|
414
|
+
.onChange(of: selectedTab) { _, _ in
|
|
415
|
+
HapticManager.shared.fire(.tabSelection, isEnabled: preferences.hapticTabSelectionEnabled)
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Design choices to keep
|
|
420
|
+
|
|
421
|
+
- Haptics should be subtle and not fire on every tiny interaction.
|
|
422
|
+
- Respect user preferences (toggle to disable).
|
|
423
|
+
- Keep haptic triggers close to the user action, not deep in data layers.
|
|
424
|
+
|
|
425
|
+
### Pitfalls
|
|
426
|
+
|
|
427
|
+
- Avoid firing multiple haptics in quick succession.
|
|
428
|
+
- Do not assume haptics are available; check support.
|
|
429
|
+
|
|
430
|
+
### Core Haptics (CHHapticEngine)
|
|
431
|
+
|
|
432
|
+
For advanced haptic patterns beyond the simple feedback generators, use Core Haptics. It provides precise control over haptic intensity, sharpness, and timing with support for audio-haptic synchronization.
|
|
433
|
+
|
|
434
|
+
> **Docs:** [CHHapticEngine](https://sosumi.ai/documentation/corehaptics/chhapticengine) · [Preparing your app to play haptics](https://sosumi.ai/documentation/corehaptics/preparing-your-app-to-play-haptics)
|
|
435
|
+
|
|
436
|
+
#### Capabilities check
|
|
437
|
+
|
|
438
|
+
Always verify hardware support before creating an engine:
|
|
439
|
+
|
|
440
|
+
```swift
|
|
441
|
+
import CoreHaptics
|
|
442
|
+
|
|
443
|
+
let supportsHaptics = CHHapticEngine.capabilitiesForHardware().supportsHaptics
|
|
444
|
+
let supportsAudio = CHHapticEngine.capabilitiesForHardware().supportsAudio
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
#### Engine setup and lifecycle
|
|
448
|
+
|
|
449
|
+
```swift
|
|
450
|
+
@MainActor
|
|
451
|
+
final class CoreHapticManager {
|
|
452
|
+
private var engine: CHHapticEngine?
|
|
453
|
+
|
|
454
|
+
func prepareEngine() throws {
|
|
455
|
+
guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { return }
|
|
456
|
+
|
|
457
|
+
engine = try CHHapticEngine()
|
|
458
|
+
|
|
459
|
+
// Called when the engine stops due to external cause (audio session interruption, app backgrounding)
|
|
460
|
+
engine?.stoppedHandler = { reason in
|
|
461
|
+
print("Haptic engine stopped: \(reason)")
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Called after the engine is reset (e.g., after audio session interruption ends)
|
|
465
|
+
engine?.resetHandler = { [weak self] in
|
|
466
|
+
do {
|
|
467
|
+
try self?.engine?.start()
|
|
468
|
+
} catch {
|
|
469
|
+
print("Failed to restart engine: \(error)")
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
try engine?.start()
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
func stopEngine() {
|
|
477
|
+
engine?.stop()
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
Key lifecycle rules:
|
|
483
|
+
- Call `engine.start()` before playing any patterns.
|
|
484
|
+
- Handle `stoppedHandler` — the system can stop the engine when your app moves to the background or during audio interruptions.
|
|
485
|
+
- Handle `resetHandler` — restart the engine when the system resets it.
|
|
486
|
+
- Call `engine.stop()` when haptics are no longer needed to save battery.
|
|
487
|
+
|
|
488
|
+
#### CHHapticPattern and CHHapticEvent
|
|
489
|
+
|
|
490
|
+
Build patterns from individual haptic and audio events:
|
|
491
|
+
|
|
492
|
+
```swift
|
|
493
|
+
func playTransientTap() throws {
|
|
494
|
+
let sharpness = CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.8)
|
|
495
|
+
let intensity = CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0)
|
|
496
|
+
|
|
497
|
+
// Transient: short, single-tap feel
|
|
498
|
+
let event = CHHapticEvent(
|
|
499
|
+
eventType: .hapticTransient,
|
|
500
|
+
parameters: [intensity, sharpness],
|
|
501
|
+
relativeTime: 0
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
let pattern = try CHHapticPattern(events: [event], parameters: [])
|
|
505
|
+
let player = try engine?.makePlayer(with: pattern)
|
|
506
|
+
try player?.start(atTime: CHHapticTimeImmediate)
|
|
507
|
+
}
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
**Event types:**
|
|
511
|
+
|
|
512
|
+
| Type | Description |
|
|
513
|
+
|------|-------------|
|
|
514
|
+
| `.hapticTransient` | Brief, tap-like impulse |
|
|
515
|
+
| `.hapticContinuous` | Sustained vibration over a `duration` |
|
|
516
|
+
| `.audioContinuous` | Sustained audio tone |
|
|
517
|
+
| `.audioCustom` | Play a custom audio resource |
|
|
518
|
+
|
|
519
|
+
**Common parameters:** `.hapticIntensity` (0–1), `.hapticSharpness` (0–1), `.attackTime`, `.decayTime`, `.releaseTime`.
|
|
520
|
+
|
|
521
|
+
#### Playing patterns with CHHapticPatternPlayer
|
|
522
|
+
|
|
523
|
+
```swift
|
|
524
|
+
func playContinuousBuzz() throws {
|
|
525
|
+
let intensity = CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.6)
|
|
526
|
+
let sharpness = CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.3)
|
|
527
|
+
|
|
528
|
+
let event = CHHapticEvent(
|
|
529
|
+
eventType: .hapticContinuous,
|
|
530
|
+
parameters: [intensity, sharpness],
|
|
531
|
+
relativeTime: 0,
|
|
532
|
+
duration: 0.5
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
let pattern = try CHHapticPattern(events: [event], parameters: [])
|
|
536
|
+
let player = try engine?.makePlayer(with: pattern)
|
|
537
|
+
try player?.start(atTime: CHHapticTimeImmediate)
|
|
538
|
+
}
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
For looping, seeking, and pausing, use `CHHapticAdvancedPatternPlayer` via `engine.makeAdvancedPlayer(with:)`.
|
|
542
|
+
|
|
543
|
+
#### Haptic parameter curves (CHHapticParameterCurve)
|
|
544
|
+
|
|
545
|
+
Smoothly vary parameters over time within a pattern:
|
|
546
|
+
|
|
547
|
+
```swift
|
|
548
|
+
func playRampingPattern() throws {
|
|
549
|
+
let event = CHHapticEvent(
|
|
550
|
+
eventType: .hapticContinuous,
|
|
551
|
+
parameters: [
|
|
552
|
+
CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.2),
|
|
553
|
+
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.1)
|
|
554
|
+
],
|
|
555
|
+
relativeTime: 0,
|
|
556
|
+
duration: 1.0
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
// Ramp intensity from 0.2 → 1.0 over 1 second
|
|
560
|
+
let curve = CHHapticParameterCurve(
|
|
561
|
+
parameterID: .hapticIntensityControl,
|
|
562
|
+
controlPoints: [
|
|
563
|
+
.init(relativeTime: 0, value: 0.2),
|
|
564
|
+
.init(relativeTime: 0.5, value: 0.7),
|
|
565
|
+
.init(relativeTime: 1.0, value: 1.0)
|
|
566
|
+
],
|
|
567
|
+
relativeTime: 0
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
let pattern = try CHHapticPattern(events: [event], parameterCurves: [curve])
|
|
571
|
+
let player = try engine?.makePlayer(with: pattern)
|
|
572
|
+
try player?.start(atTime: CHHapticTimeImmediate)
|
|
573
|
+
}
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
#### Audio-haptic synchronization (AHAP files)
|
|
577
|
+
|
|
578
|
+
AHAP (Apple Haptic and Audio Pattern) files define haptic patterns in JSON for easy authoring and design iteration. Load them directly:
|
|
579
|
+
|
|
580
|
+
```swift
|
|
581
|
+
func playAHAPFile() throws {
|
|
582
|
+
guard let url = Bundle.main.url(forResource: "success", withExtension: "ahap") else { return }
|
|
583
|
+
try engine?.playPattern(from: url)
|
|
584
|
+
}
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
AHAP files support the same events, parameters, and parameter curves as the programmatic API. Use the **Core Haptics** design tools in Xcode to preview patterns.
|
|
588
|
+
|
|
589
|
+
> **Docs:** [Representing haptic patterns in AHAP files](https://sosumi.ai/documentation/corehaptics/representing-haptic-patterns-in-ahap-files)
|
|
590
|
+
|
|
591
|
+
## Matched Transitions
|
|
592
|
+
|
|
593
|
+
### Intent
|
|
594
|
+
|
|
595
|
+
Use matched transitions to create smooth continuity between a source view (thumbnail, avatar) and a destination view (sheet, detail, viewer).
|
|
596
|
+
|
|
597
|
+
### Core patterns
|
|
598
|
+
|
|
599
|
+
- Use a shared `Namespace` and a stable ID for the source.
|
|
600
|
+
- Use `matchedTransitionSource` + `navigationTransition(.zoom(...))` on iOS 26+.
|
|
601
|
+
- Use `matchedGeometryEffect` for in-place transitions within a view hierarchy.
|
|
602
|
+
- Keep IDs stable across view updates (avoid random UUIDs).
|
|
603
|
+
|
|
604
|
+
### Example: media preview to full-screen viewer (iOS 26+)
|
|
605
|
+
|
|
606
|
+
```swift
|
|
607
|
+
struct MediaPreview: View {
|
|
608
|
+
@Namespace private var namespace
|
|
609
|
+
@State private var selected: MediaAttachment?
|
|
610
|
+
|
|
611
|
+
var body: some View {
|
|
612
|
+
ThumbnailView()
|
|
613
|
+
.matchedTransitionSource(id: selected?.id ?? "", in: namespace)
|
|
614
|
+
.sheet(item: $selected) { item in
|
|
615
|
+
MediaViewer(item: item)
|
|
616
|
+
.navigationTransition(.zoom(sourceID: item.id, in: namespace))
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
### Example: matched geometry within a view
|
|
623
|
+
|
|
624
|
+
```swift
|
|
625
|
+
struct ToggleBadge: View {
|
|
626
|
+
@Namespace private var space
|
|
627
|
+
@State private var isOn = false
|
|
628
|
+
|
|
629
|
+
var body: some View {
|
|
630
|
+
Button {
|
|
631
|
+
withAnimation(.spring) { isOn.toggle() }
|
|
632
|
+
} label: {
|
|
633
|
+
Image(systemName: isOn ? "eye" : "eye.slash")
|
|
634
|
+
.matchedGeometryEffect(id: "icon", in: space)
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
### Design choices to keep
|
|
641
|
+
|
|
642
|
+
- Prefer `matchedTransitionSource` for cross-screen transitions.
|
|
643
|
+
- Keep source and destination sizes reasonable to avoid jarring scale changes.
|
|
644
|
+
- Use `withAnimation` for state-driven transitions.
|
|
645
|
+
|
|
646
|
+
### Pitfalls
|
|
647
|
+
|
|
648
|
+
- Don’t use unstable IDs; it breaks the transition.
|
|
649
|
+
- Avoid mismatched shapes (e.g., square to circle) unless the design expects it.
|
|
650
|
+
|
|
651
|
+
## Loading and Placeholders
|
|
652
|
+
|
|
653
|
+
Use this when a view needs a consistent loading state (skeletons, redaction, empty state) without blocking interaction.
|
|
654
|
+
|
|
655
|
+
### Patterns to prefer
|
|
656
|
+
|
|
657
|
+
- **Redacted placeholders** for list/detail content to preserve layout while loading.
|
|
658
|
+
- **ContentUnavailableView** for empty or error states after loading completes.
|
|
659
|
+
- **ProgressView** only for short, global operations (use sparingly in content-heavy screens).
|
|
660
|
+
|
|
661
|
+
### Recommended approach
|
|
662
|
+
|
|
663
|
+
1. Keep the real layout, render placeholder data, then apply `.redacted(reason: .placeholder)`.
|
|
664
|
+
2. For lists, show a fixed number of placeholder rows (avoid infinite spinners).
|
|
665
|
+
3. Switch to `ContentUnavailableView` when load finishes but data is empty.
|
|
666
|
+
|
|
667
|
+
### Pitfalls
|
|
668
|
+
|
|
669
|
+
- Don’t animate layout shifts during redaction; keep frames stable.
|
|
670
|
+
- Avoid nesting multiple spinners; use one loading indicator per section.
|
|
671
|
+
- Keep placeholder count small (3–6) to reduce jank on low-end devices.
|
|
672
|
+
|
|
673
|
+
### Minimal usage
|
|
674
|
+
|
|
675
|
+
```swift
|
|
676
|
+
VStack {
|
|
677
|
+
if isLoading {
|
|
678
|
+
ForEach(0..<3, id: \.self) { _ in
|
|
679
|
+
RowView(model: .placeholder())
|
|
680
|
+
}
|
|
681
|
+
.redacted(reason: .placeholder)
|
|
682
|
+
} else if items.isEmpty {
|
|
683
|
+
ContentUnavailableView("No items", systemImage: "tray")
|
|
684
|
+
} else {
|
|
685
|
+
ForEach(items) { item in RowView(model: item) }
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
## Focus Handling
|
|
691
|
+
|
|
692
|
+
### Intent
|
|
693
|
+
|
|
694
|
+
Use `@FocusState` to control keyboard focus, chain fields, and coordinate focus across complex forms.
|
|
695
|
+
|
|
696
|
+
### Core patterns
|
|
697
|
+
|
|
698
|
+
- Use an enum to represent focusable fields.
|
|
699
|
+
- Set initial focus in `onAppear`.
|
|
700
|
+
- Use `.onSubmit` to move focus to the next field.
|
|
701
|
+
- For dynamic lists of fields, use an enum with associated values (e.g., `.option(Int)`).
|
|
702
|
+
|
|
703
|
+
### Example: single field focus
|
|
704
|
+
|
|
705
|
+
```swift
|
|
706
|
+
struct AddServerView: View {
|
|
707
|
+
@State private var server = ""
|
|
708
|
+
@FocusState private var isServerFieldFocused: Bool
|
|
709
|
+
|
|
710
|
+
var body: some View {
|
|
711
|
+
Form {
|
|
712
|
+
TextField("Server", text: $server)
|
|
713
|
+
.focused($isServerFieldFocused)
|
|
714
|
+
}
|
|
715
|
+
.onAppear { isServerFieldFocused = true }
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
### Example: chained focus with enum
|
|
721
|
+
|
|
722
|
+
```swift
|
|
723
|
+
struct EditTagView: View {
|
|
724
|
+
enum FocusField { case title, symbol, newTag }
|
|
725
|
+
@FocusState private var focusedField: FocusField?
|
|
726
|
+
|
|
727
|
+
var body: some View {
|
|
728
|
+
Form {
|
|
729
|
+
TextField("Title", text: $title)
|
|
730
|
+
.focused($focusedField, equals: .title)
|
|
731
|
+
.onSubmit { focusedField = .symbol }
|
|
732
|
+
|
|
733
|
+
TextField("Symbol", text: $symbol)
|
|
734
|
+
.focused($focusedField, equals: .symbol)
|
|
735
|
+
.onSubmit { focusedField = .newTag }
|
|
736
|
+
}
|
|
737
|
+
.onAppear { focusedField = .title }
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
### Example: dynamic focus for variable fields
|
|
743
|
+
|
|
744
|
+
```swift
|
|
745
|
+
struct PollView: View {
|
|
746
|
+
enum FocusField: Hashable { case option(Int) }
|
|
747
|
+
@FocusState private var focused: FocusField?
|
|
748
|
+
@State private var options: [String] = ["", ""]
|
|
749
|
+
@State private var currentIndex = 0
|
|
750
|
+
|
|
751
|
+
var body: some View {
|
|
752
|
+
ForEach(options.indices, id: \.self) { index in
|
|
753
|
+
TextField("Option \(index + 1)", text: $options[index])
|
|
754
|
+
.focused($focused, equals: .option(index))
|
|
755
|
+
.onSubmit { addOption(at: index) }
|
|
756
|
+
}
|
|
757
|
+
.onAppear { focused = .option(0) }
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
private func addOption(at index: Int) {
|
|
761
|
+
options.append("")
|
|
762
|
+
currentIndex = index + 1
|
|
763
|
+
Task { @MainActor in
|
|
764
|
+
try? await Task.sleep(for: .milliseconds(10))
|
|
765
|
+
focused = .option(currentIndex)
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
### Design choices to keep
|
|
772
|
+
|
|
773
|
+
- Keep focus state local to the view that owns the fields.
|
|
774
|
+
- Use focus changes to drive UX (validation messages, helper UI).
|
|
775
|
+
- Pair with `.scrollDismissesKeyboard(...)` when using ScrollView/Form.
|
|
776
|
+
|
|
777
|
+
### Pitfalls
|
|
778
|
+
|
|
779
|
+
- Don’t store focus state in shared objects; it is view-local.
|
|
780
|
+
- Avoid aggressive focus changes during animation; delay if needed.
|