@mmerterden/multi-agent-pipeline 20.6.0 → 20.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/LICENSE +0 -10
- package/docs/facts.json +1 -1
- package/manifest.json +328 -329
- package/package.json +2 -2
- package/pipeline/multi-agent-refs/features/design-conformance.md +62 -64
- package/pipeline/scripts/_notices.mjs +12 -1
- package/pipeline/scripts/gen-skills-index.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +106 -106
- package/pipeline/skills/shared/README.md +71 -71
- package/pipeline/skills/shared/external/agent-introspection-debugging/SKILL.md +1 -0
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +373 -381
- package/pipeline/skills/shared/external/alarmkit/evals/evals.json +23 -18
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-patterns.md +328 -378
- package/pipeline/skills/shared/external/android-architecture/SKILL.md +2 -0
- package/pipeline/skills/shared/external/android-performance/SKILL.md +2 -0
- package/pipeline/skills/shared/external/android-security/SKILL.md +2 -0
- package/pipeline/skills/shared/external/app-clips/SKILL.md +260 -160
- package/pipeline/skills/shared/external/app-clips/evals/evals.json +27 -27
- package/pipeline/skills/shared/external/app-clips/references/data-handoff-notifications-location.md +150 -83
- package/pipeline/skills/shared/external/app-clips/references/routing-and-experiences.md +135 -83
- package/pipeline/skills/shared/external/app-clips/references/size-capabilities-and-promotion.md +143 -85
- package/pipeline/skills/shared/external/app-intents/SKILL.md +302 -304
- package/pipeline/skills/shared/external/app-intents/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-intents/references/appintents-advanced.md +594 -894
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +339 -277
- package/pipeline/skills/shared/external/app-store-optimization/evals/evals.json +27 -23
- package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +105 -122
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +143 -166
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +307 -326
- package/pipeline/skills/shared/external/app-store-review/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/app-store-review/references/privacy-manifest.md +105 -67
- package/pipeline/skills/shared/external/app-store-review/references/review-checklists.md +114 -101
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +333 -360
- package/pipeline/skills/shared/external/apple-on-device-ai/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-conversion.md +215 -322
- package/pipeline/skills/shared/external/apple-on-device-ai/references/coreml-optimization.md +161 -256
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models.md +277 -387
- package/pipeline/skills/shared/external/apple-on-device-ai/references/mlx-swift.md +196 -210
- package/pipeline/skills/shared/external/authentication/SKILL.md +265 -381
- package/pipeline/skills/shared/external/authentication/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +133 -178
- package/pipeline/skills/shared/external/authentication/references/passkeys.md +111 -147
- package/pipeline/skills/shared/external/avkit/SKILL.md +267 -364
- package/pipeline/skills/shared/external/avkit/evals/evals.json +26 -26
- package/pipeline/skills/shared/external/avkit/references/avkit-patterns.md +375 -493
- package/pipeline/skills/shared/external/background-processing/SKILL.md +270 -382
- package/pipeline/skills/shared/external/background-processing/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +169 -317
- package/pipeline/skills/shared/external/backlog/BACKLOG.md +1 -1
- package/pipeline/skills/shared/external/backlog/SKILL.md +56 -33
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +290 -371
- package/pipeline/skills/shared/external/callkit-voip/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/callkit-voip/references/callkit-patterns.md +175 -343
- package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +1 -0
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +292 -381
- package/pipeline/skills/shared/external/cloudkit-sync/evals/evals.json +33 -30
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-patterns.md +227 -355
- package/pipeline/skills/shared/external/compose-components/SKILL.md +2 -0
- package/pipeline/skills/shared/external/compose-navigation/SKILL.md +3 -2
- package/pipeline/skills/shared/external/compose-testing/SKILL.md +2 -0
- package/pipeline/skills/shared/external/contacts-framework/SKILL.md +197 -346
- package/pipeline/skills/shared/external/contacts-framework/evals/evals.json +19 -21
- package/pipeline/skills/shared/external/contacts-framework/references/contacts-patterns.md +169 -308
- package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +226 -376
- package/pipeline/skills/shared/external/core-bluetooth/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/core-bluetooth/references/ble-patterns.md +257 -337
- package/pipeline/skills/shared/external/core-data/SKILL.md +292 -368
- package/pipeline/skills/shared/external/core-data/evals/evals.json +30 -27
- package/pipeline/skills/shared/external/core-motion/SKILL.md +235 -324
- package/pipeline/skills/shared/external/core-motion/evals/evals.json +31 -27
- package/pipeline/skills/shared/external/core-motion/references/motion-patterns.md +210 -310
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +292 -366
- package/pipeline/skills/shared/external/core-nfc/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/core-nfc/references/nfc-patterns.md +233 -329
- package/pipeline/skills/shared/external/coreml/SKILL.md +323 -367
- package/pipeline/skills/shared/external/coreml/evals/evals.json +24 -21
- package/pipeline/skills/shared/external/coreml/references/coreml-swift-integration.md +562 -565
- package/pipeline/skills/shared/external/council/SKILL.md +1 -0
- package/pipeline/skills/shared/external/cryptokit/SKILL.md +253 -394
- package/pipeline/skills/shared/external/cryptokit/evals/evals.json +20 -18
- package/pipeline/skills/shared/external/cryptokit/references/cryptokit-patterns.md +299 -488
- package/pipeline/skills/shared/external/css-modern/SKILL.md +1 -0
- package/pipeline/skills/shared/external/database-patterns/SKILL.md +1 -0
- package/pipeline/skills/shared/external/debugging-instruments/SKILL.md +270 -323
- package/pipeline/skills/shared/external/debugging-instruments/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/debugging-instruments/references/instruments-guide.md +167 -315
- package/pipeline/skills/shared/external/debugging-instruments/references/lldb-patterns.md +140 -193
- package/pipeline/skills/shared/external/device-integrity/SKILL.md +230 -353
- package/pipeline/skills/shared/external/device-integrity/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/device-integrity/references/device-integrity-patterns.md +159 -197
- package/pipeline/skills/shared/external/energykit/SKILL.md +225 -392
- package/pipeline/skills/shared/external/energykit/evals/evals.json +29 -28
- package/pipeline/skills/shared/external/energykit/references/energykit-patterns.md +174 -470
- package/pipeline/skills/shared/external/eventkit-calendar/SKILL.md +261 -383
- package/pipeline/skills/shared/external/eventkit-calendar/evals/evals.json +25 -22
- package/pipeline/skills/shared/external/eventkit-calendar/references/eventkit-patterns.md +165 -268
- package/pipeline/skills/shared/external/evidence-github/SKILL.md +2 -0
- package/pipeline/skills/shared/external/evidence-registry/SKILL.md +2 -0
- package/pipeline/skills/shared/external/gradle-kotlin-dsl/SKILL.md +2 -0
- package/pipeline/skills/shared/external/healthkit/SKILL.md +252 -303
- package/pipeline/skills/shared/external/healthkit/evals/evals.json +24 -23
- package/pipeline/skills/shared/external/healthkit/references/healthkit-patterns.md +369 -523
- package/pipeline/skills/shared/external/homekit-matter/SKILL.md +233 -348
- package/pipeline/skills/shared/external/homekit-matter/evals/evals.json +27 -22
- package/pipeline/skills/shared/external/homekit-matter/references/matter-commissioning.md +199 -305
- package/pipeline/skills/shared/external/html-semantic/SKILL.md +1 -0
- package/pipeline/skills/shared/external/humanizer/SKILL.md +1 -0
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +368 -340
- package/pipeline/skills/shared/external/ios-accessibility/evals/evals.json +28 -27
- package/pipeline/skills/shared/external/ios-accessibility/references/a11y-patterns.md +314 -260
- package/pipeline/skills/shared/external/ios-accessibility/references/media-accessibility.md +97 -67
- package/pipeline/skills/shared/external/ios-accessibility/references/nutrition-labels.md +165 -101
- package/pipeline/skills/shared/external/ios-coding-standard/SKILL.md +1 -0
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +258 -371
- package/pipeline/skills/shared/external/ios-localization/evals/evals.json +23 -23
- package/pipeline/skills/shared/external/ios-localization/references/formatstyle-locale.md +283 -491
- package/pipeline/skills/shared/external/ios-localization/references/string-catalogs.md +313 -440
- package/pipeline/skills/shared/external/ios-module-structure/SKILL.md +1 -0
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +265 -341
- package/pipeline/skills/shared/external/ios-networking/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/ios-networking/references/background-websocket.md +425 -652
- package/pipeline/skills/shared/external/ios-networking/references/file-storage-patterns.md +143 -285
- package/pipeline/skills/shared/external/ios-networking/references/lightweight-clients.md +93 -53
- package/pipeline/skills/shared/external/ios-networking/references/network-framework.md +231 -456
- package/pipeline/skills/shared/external/ios-networking/references/urlsession-patterns.md +517 -784
- package/pipeline/skills/shared/external/ios-security/SKILL.md +2 -0
- package/pipeline/skills/shared/external/ios-simulator/SKILL.md +265 -393
- package/pipeline/skills/shared/external/ios-simulator/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/ios-simulator/references/simctl-commands.md +177 -270
- package/pipeline/skills/shared/external/live-activities/SKILL.md +318 -360
- package/pipeline/skills/shared/external/live-activities/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/live-activities/references/activitykit-patterns.md +478 -710
- package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +91 -283
- package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +119 -151
- package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +60 -90
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +119 -156
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +726 -787
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +253 -288
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +243 -304
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +88 -104
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +181 -235
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +198 -263
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +461 -466
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +145 -151
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +123 -141
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +146 -157
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/snapshot-resources.sh +22 -19
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +156 -140
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +295 -267
- package/pipeline/skills/shared/external/mapkit-location/evals/evals.json +28 -24
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-corelocation-patterns.md +378 -532
- package/pipeline/skills/shared/external/mapkit-location/references/mapkit-patterns.md +397 -499
- package/pipeline/skills/shared/external/metrickit-diagnostics/SKILL.md +165 -348
- package/pipeline/skills/shared/external/metrickit-diagnostics/evals/evals.json +26 -23
- package/pipeline/skills/shared/external/metrickit-diagnostics/references/metrickit-patterns.md +123 -130
- package/pipeline/skills/shared/external/musickit-audio/SKILL.md +189 -315
- package/pipeline/skills/shared/external/musickit-audio/evals/evals.json +22 -21
- package/pipeline/skills/shared/external/musickit-audio/references/musickit-patterns.md +181 -270
- package/pipeline/skills/shared/external/natural-language/SKILL.md +188 -340
- package/pipeline/skills/shared/external/natural-language/evals/evals.json +21 -21
- package/pipeline/skills/shared/external/natural-language/references/translation-patterns.md +171 -225
- package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +1 -0
- package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +258 -392
- package/pipeline/skills/shared/external/passkit-wallet/evals/evals.json +30 -29
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +164 -231
- package/pipeline/skills/shared/external/pdfkit/SKILL.md +312 -344
- package/pipeline/skills/shared/external/pdfkit/evals/evals.json +19 -19
- package/pipeline/skills/shared/external/pdfkit/references/pdfkit-patterns.md +413 -624
- package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +242 -358
- package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +25 -21
- package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +161 -226
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +282 -400
- package/pipeline/skills/shared/external/permissionkit/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +237 -350
- package/pipeline/skills/shared/external/photos-camera-media/SKILL.md +276 -325
- package/pipeline/skills/shared/external/photos-camera-media/references/av-playback.md +299 -545
- package/pipeline/skills/shared/external/photos-camera-media/references/camera-capture.md +344 -588
- package/pipeline/skills/shared/external/photos-camera-media/references/image-loading-caching.md +316 -660
- package/pipeline/skills/shared/external/photos-camera-media/references/photokit-patterns.md +270 -416
- package/pipeline/skills/shared/external/play-store-review/SKILL.md +2 -0
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +312 -340
- package/pipeline/skills/shared/external/push-notifications/evals/evals.json +27 -26
- package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +328 -485
- package/pipeline/skills/shared/external/push-notifications/references/rich-notifications.md +327 -560
- package/pipeline/skills/shared/external/python-patterns/SKILL.md +1 -0
- package/pipeline/skills/shared/external/react-best-practices/SKILL.md +1 -0
- package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +218 -410
- package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/realitykit-ar/references/realitykit-patterns.md +221 -348
- package/pipeline/skills/shared/external/rest-api-design/SKILL.md +1 -0
- package/pipeline/skills/shared/external/retrofit-networking/SKILL.md +2 -0
- package/pipeline/skills/shared/external/room-database/SKILL.md +2 -0
- package/pipeline/skills/shared/external/search-first/SKILL.md +1 -0
- package/pipeline/skills/shared/external/shareplay-activities/SKILL.md +222 -393
- package/pipeline/skills/shared/external/shareplay-activities/evals/evals.json +23 -24
- package/pipeline/skills/shared/external/shareplay-activities/references/shareplay-patterns.md +280 -420
- package/pipeline/skills/shared/external/signal-community/SKILL.md +2 -0
- package/pipeline/skills/shared/external/skill-creator/SKILL.md +80 -41
- package/pipeline/skills/shared/external/skill-creator/audit.md +63 -59
- package/pipeline/skills/shared/external/skill-creator/checklist.md +28 -20
- package/pipeline/skills/shared/external/skill-creator/examples.md +40 -40
- package/pipeline/skills/shared/external/skill-creator/label-check.md +48 -36
- package/pipeline/skills/shared/external/skill-creator/scripts/audit-panel.js +91 -100
- package/pipeline/skills/shared/external/skill-creator/template.md +51 -39
- package/pipeline/skills/shared/external/speech-recognition/SKILL.md +217 -421
- package/pipeline/skills/shared/external/speech-recognition/evals/evals.json +23 -26
- package/pipeline/skills/shared/external/speech-recognition/references/speechanalyzer-patterns.md +133 -125
- package/pipeline/skills/shared/external/storekit/SKILL.md +228 -204
- package/pipeline/skills/shared/external/storekit/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/storekit/references/app-review-guidelines.md +98 -109
- package/pipeline/skills/shared/external/storekit/references/core-patterns.md +298 -242
- package/pipeline/skills/shared/external/storekit/references/storekit-advanced.md +356 -649
- package/pipeline/skills/shared/external/swift-api-design-guidelines/SKILL.md +274 -399
- package/pipeline/skills/shared/external/swift-api-design-guidelines/evals/evals.json +22 -24
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/argument-labels-and-parameters.md +107 -108
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/conventions-and-special-rules.md +93 -165
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/naming-and-clarity.md +99 -137
- package/pipeline/skills/shared/external/swift-api-design-guidelines/references/side-effects-and-mutating-pairs.md +77 -120
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +334 -350
- package/pipeline/skills/shared/external/swift-architecture/evals/evals.json +22 -22
- package/pipeline/skills/shared/external/swift-charts/SKILL.md +208 -394
- package/pipeline/skills/shared/external/swift-charts/evals/evals.json +27 -30
- package/pipeline/skills/shared/external/swift-charts/references/charts-patterns.md +351 -762
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +339 -343
- package/pipeline/skills/shared/external/swift-codable/evals/evals.json +20 -20
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +303 -351
- package/pipeline/skills/shared/external/swift-concurrency/evals/evals.json +27 -24
- package/pipeline/skills/shared/external/swift-concurrency/references/approachable-concurrency.md +65 -80
- package/pipeline/skills/shared/external/swift-concurrency/references/async-algorithms.md +48 -84
- package/pipeline/skills/shared/external/swift-concurrency/references/bridging-interop.md +134 -79
- package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +145 -167
- package/pipeline/skills/shared/external/swift-concurrency/references/diagnostics.md +62 -50
- package/pipeline/skills/shared/external/swift-concurrency/references/swiftui-concurrency.md +92 -121
- package/pipeline/skills/shared/external/swift-concurrency/references/synchronization-primitives.md +177 -241
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +258 -234
- package/pipeline/skills/shared/external/swift-language/SKILL.md +342 -382
- package/pipeline/skills/shared/external/swift-language/evals/evals.json +24 -27
- package/pipeline/skills/shared/external/swift-language/references/swift-attributes-interop.md +79 -56
- package/pipeline/skills/shared/external/swift-language/references/swift-patterns-extended.md +297 -340
- package/pipeline/skills/shared/external/swift-security/SKILL.md +180 -161
- package/pipeline/skills/shared/external/swift-security/evals/evals.json +25 -25
- package/pipeline/skills/shared/external/swift-security/references/biometric-authentication.md +314 -469
- package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +408 -476
- package/pipeline/skills/shared/external/swift-security/references/common-anti-patterns.md +260 -530
- package/pipeline/skills/shared/external/swift-security/references/compliance-owasp-mapping.md +270 -477
- package/pipeline/skills/shared/external/swift-security/references/credential-storage-patterns.md +573 -571
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-public-key.md +370 -441
- package/pipeline/skills/shared/external/swift-security/references/cryptokit-symmetric.md +332 -433
- package/pipeline/skills/shared/external/swift-security/references/keychain-access-control.md +346 -468
- package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +352 -472
- package/pipeline/skills/shared/external/swift-security/references/keychain-item-classes.md +431 -432
- package/pipeline/skills/shared/external/swift-security/references/keychain-sharing.md +328 -425
- package/pipeline/skills/shared/external/swift-security/references/migration-legacy-stores.md +341 -579
- package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +396 -457
- package/pipeline/skills/shared/external/swift-security/references/testing-security-code.md +354 -614
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +188 -175
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +26 -24
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +80 -84
- package/pipeline/skills/shared/external/swift-testing/references/testing-patterns.md +317 -433
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +392 -256
- package/pipeline/skills/shared/external/swiftdata/evals/evals.json +24 -24
- package/pipeline/skills/shared/external/swiftdata/references/core-data-coexistence.md +206 -402
- package/pipeline/skills/shared/external/swiftdata/references/indexing.md +59 -52
- package/pipeline/skills/shared/external/swiftdata/references/predicate-pitfalls.md +57 -33
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-advanced.md +354 -747
- package/pipeline/skills/shared/external/swiftdata/references/swiftdata-queries.md +300 -508
- package/pipeline/skills/shared/external/swiftlint/SKILL.md +175 -226
- package/pipeline/skills/shared/external/swiftlint/references/adoption-and-configuration.md +141 -208
- package/pipeline/skills/shared/external/swiftlint/references/custom-rules-and-analyze.md +100 -109
- package/pipeline/skills/shared/external/swiftlint/references/plugins-run-scripts-and-integrations.md +159 -179
- package/pipeline/skills/shared/external/swiftlint/references/rule-reference.md +383 -18
- package/pipeline/skills/shared/external/swiftlint/references/rules-suppressions-and-baselines.md +143 -229
- package/pipeline/skills/shared/external/swiftui-animation/SKILL.md +283 -366
- package/pipeline/skills/shared/external/swiftui-animation/references/animation-advanced.md +396 -608
- package/pipeline/skills/shared/external/swiftui-animation/references/core-animation-bridge.md +336 -385
- package/pipeline/skills/shared/external/swiftui-gestures/SKILL.md +239 -349
- package/pipeline/skills/shared/external/swiftui-gestures/references/gesture-patterns.md +228 -310
- package/pipeline/skills/shared/external/swiftui-layout-components/SKILL.md +260 -249
- package/pipeline/skills/shared/external/swiftui-layout-components/references/form.md +92 -74
- package/pipeline/skills/shared/external/swiftui-layout-components/references/grids.md +112 -177
- package/pipeline/skills/shared/external/swiftui-layout-components/references/list.md +61 -64
- package/pipeline/skills/shared/external/swiftui-layout-components/references/scrollview.md +94 -134
- package/pipeline/skills/shared/external/swiftui-liquid-glass/SKILL.md +193 -225
- package/pipeline/skills/shared/external/swiftui-liquid-glass/references/liquid-glass.md +173 -327
- package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +193 -168
- package/pipeline/skills/shared/external/swiftui-navigation/references/deeplinks.md +127 -150
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +132 -133
- package/pipeline/skills/shared/external/swiftui-navigation/references/sheets.md +152 -117
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +106 -140
- package/pipeline/skills/shared/external/swiftui-patterns/SKILL.md +316 -252
- package/pipeline/skills/shared/external/swiftui-patterns/references/architecture-patterns.md +341 -332
- package/pipeline/skills/shared/external/swiftui-patterns/references/deprecated-migration.md +547 -854
- package/pipeline/skills/shared/external/swiftui-patterns/references/design-polish.md +485 -537
- package/pipeline/skills/shared/external/swiftui-patterns/references/platform-and-sharing.md +417 -499
- package/pipeline/skills/shared/external/swiftui-performance/SKILL.md +213 -376
- package/pipeline/skills/shared/external/swiftui-performance/references/demystify-swiftui-performance-wwdc23.md +86 -175
- package/pipeline/skills/shared/external/swiftui-performance/references/optimizing-swiftui-performance-instruments.md +89 -195
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-hangs-in-your-app.md +95 -182
- package/pipeline/skills/shared/external/swiftui-performance/references/understanding-improving-swiftui-performance.md +71 -149
- package/pipeline/skills/shared/external/swiftui-performance/references/wwdc-session-sources.md +21 -27
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +303 -295
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/hosting-migration.md +204 -387
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/representable-recipes.md +469 -683
- package/pipeline/skills/shared/external/swiftui-webkit/SKILL.md +140 -186
- package/pipeline/skills/shared/external/swiftui-webkit/references/loading-and-observation.md +75 -86
- package/pipeline/skills/shared/external/swiftui-webkit/references/local-content-and-custom-schemes.md +63 -60
- package/pipeline/skills/shared/external/swiftui-webkit/references/migration-and-fallbacks.md +69 -137
- package/pipeline/skills/shared/external/swiftui-webkit/references/navigation-and-javascript.md +95 -67
- package/pipeline/skills/shared/external/tailwind-css/SKILL.md +1 -0
- package/pipeline/skills/shared/external/testing-backend/SKILL.md +1 -0
- package/pipeline/skills/shared/external/tipkit/SKILL.md +220 -335
- package/pipeline/skills/shared/external/tipkit/references/tipkit-patterns.md +356 -494
- package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +1 -0
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +260 -375
- package/pipeline/skills/shared/external/vision-framework/references/vision-requests.md +393 -515
- package/pipeline/skills/shared/external/vision-framework/references/visionkit-scanner.md +363 -539
- package/pipeline/skills/shared/external/vue-composition/SKILL.md +1 -0
- package/pipeline/skills/shared/external/weatherkit/SKILL.md +152 -310
- package/pipeline/skills/shared/external/weatherkit/references/weatherkit-patterns.md +288 -407
- package/pipeline/skills/shared/external/web-accessibility/SKILL.md +1 -0
- package/pipeline/skills/shared/external/web-performance/SKILL.md +1 -0
- package/pipeline/skills/shared/external/web-testing/SKILL.md +1 -0
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +216 -288
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +414 -719
- package/pipeline/skills/shared/external/NOTICE-swift-ios-skills.md +0 -39
|
@@ -1,475 +1,362 @@
|
|
|
1
|
-
# PermissionKit
|
|
1
|
+
# PermissionKit patterns
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Longer examples that back the main `permissionkit` skill. They assume the iOS
|
|
4
|
+
26.2 SDK (macOS 26.2 for the AppKit section) unless marked otherwise; the
|
|
5
|
+
26.0 pieces (`CommunicationLimits`, the topic and question types) work
|
|
6
|
+
earlier. Check signatures against the SDK you build with.
|
|
7
|
+
|
|
8
|
+
One rule runs through every example: a child who cancels the iMessage send
|
|
9
|
+
sheet produces no response at all. Pending state therefore has to be explicit
|
|
10
|
+
and has to be able to expire.
|
|
5
11
|
|
|
6
12
|
## Contents
|
|
7
13
|
|
|
8
|
-
- [
|
|
9
|
-
- [Response
|
|
10
|
-
- [
|
|
11
|
-
- [
|
|
12
|
-
- [SwiftUI
|
|
13
|
-
- [macOS
|
|
14
|
-
- [Error
|
|
14
|
+
- [UIKit view controller](#uikit-view-controller)
|
|
15
|
+
- [Response manager](#response-manager)
|
|
16
|
+
- [Several contacts in one question](#several-contacts-in-one-question)
|
|
17
|
+
- [Limits-check view model](#limits-check-view-model)
|
|
18
|
+
- [SwiftUI full flow](#swiftui-full-flow)
|
|
19
|
+
- [macOS](#macos)
|
|
20
|
+
- [Error recovery](#error-recovery)
|
|
15
21
|
|
|
16
|
-
##
|
|
22
|
+
## UIKit view controller
|
|
17
23
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
PermissionKit does not deliver a `PermissionResponse` for that question.
|
|
24
|
+
The controller owns one long-lived observer task, started when the view loads
|
|
25
|
+
and cancelled when the controller goes away.
|
|
21
26
|
|
|
22
27
|
```swift
|
|
23
28
|
import UIKit
|
|
24
|
-
import PermissionKit
|
|
29
|
+
@preconcurrency import PermissionKit
|
|
25
30
|
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
@available(iOS 26.2, *)
|
|
32
|
+
final class TeammateViewController: UIViewController {
|
|
33
|
+
private var answerTask: Task<Void, Never>?
|
|
34
|
+
private let teammate: Teammate
|
|
28
35
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
init(teammate: Teammate) {
|
|
37
|
+
self.teammate = teammate
|
|
38
|
+
super.init(nibName: nil, bundle: nil)
|
|
32
39
|
}
|
|
40
|
+
required init?(coder: NSCoder) { nil }
|
|
33
41
|
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
override func viewDidLoad() {
|
|
43
|
+
super.viewDidLoad()
|
|
44
|
+
answerTask = Task { [weak self] in
|
|
45
|
+
let answers = AskCenter.shared.responses(for: CommunicationTopic.self)
|
|
46
|
+
for await answer in answers {
|
|
47
|
+
await MainActor.run { self?.apply(answer) }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
36
50
|
}
|
|
37
51
|
|
|
38
|
-
|
|
39
|
-
let personInfo = CommunicationTopic.PersonInformation(
|
|
40
|
-
handle: CommunicationHandle(
|
|
41
|
-
value: contact.phoneNumber,
|
|
42
|
-
kind: .phoneNumber
|
|
43
|
-
),
|
|
44
|
-
nameComponents: contact.nameComponents,
|
|
45
|
-
avatarImage: contact.avatarCGImage
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
let topic = CommunicationTopic(
|
|
49
|
-
personInformation: [personInfo],
|
|
50
|
-
actions: [.message]
|
|
51
|
-
)
|
|
52
|
+
deinit { answerTask?.cancel() }
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
func askToMessage() {
|
|
55
|
+
let person = CommunicationTopic.PersonInformation(
|
|
56
|
+
handle: CommunicationHandle(value: teammate.phone, kind: .phoneNumber),
|
|
57
|
+
nameComponents: teammate.name,
|
|
58
|
+
avatarImage: teammate.avatarCGImage
|
|
55
59
|
)
|
|
60
|
+
let topic = CommunicationTopic(personInformation: [person], actions: [.message])
|
|
61
|
+
let request = PermissionQuestion<CommunicationTopic>(communicationTopic: topic)
|
|
56
62
|
|
|
57
63
|
Task {
|
|
58
64
|
do {
|
|
59
|
-
try await AskCenter.shared.ask(
|
|
60
|
-
|
|
61
|
-
|
|
65
|
+
try await AskCenter.shared.ask(request, in: self as UIViewController)
|
|
66
|
+
showWaitingBadge()
|
|
67
|
+
scheduleExpiry(for: request.id)
|
|
62
68
|
} catch AskError.communicationLimitsNotEnabled {
|
|
63
|
-
enableMessaging(for:
|
|
69
|
+
enableMessaging(for: person.handle)
|
|
64
70
|
} catch AskError.notAvailable {
|
|
65
|
-
|
|
71
|
+
showUnavailableBanner()
|
|
66
72
|
} catch {
|
|
67
73
|
showError(error)
|
|
68
74
|
}
|
|
69
75
|
}
|
|
70
76
|
}
|
|
71
77
|
|
|
72
|
-
private func startObservingResponses() {
|
|
73
|
-
responseTask = Task { [weak self] in
|
|
74
|
-
let responses = AskCenter.shared.responses(
|
|
75
|
-
for: CommunicationTopic.self
|
|
76
|
-
)
|
|
77
|
-
for await response in responses {
|
|
78
|
-
await MainActor.run {
|
|
79
|
-
self?.handleResponse(response)
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
78
|
@MainActor
|
|
86
|
-
private func
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
for handle
|
|
92
|
-
|
|
79
|
+
private func apply(_ answer: PermissionResponse<CommunicationTopic>) {
|
|
80
|
+
let people = answer.question.topic.personInformation
|
|
81
|
+
for handle in people.map(\.handle) {
|
|
82
|
+
switch answer.choice.answer {
|
|
83
|
+
case .approval: enableMessaging(for: handle)
|
|
84
|
+
case .denial: showDenied(for: handle)
|
|
85
|
+
@unknown default: showDenied(for: handle)
|
|
93
86
|
}
|
|
94
|
-
case .denial:
|
|
95
|
-
let handles = response.question.topic.personInformation
|
|
96
|
-
.map(\.handle)
|
|
97
|
-
for handle in handles {
|
|
98
|
-
showDeniedState(for: handle)
|
|
99
|
-
}
|
|
100
|
-
@unknown default:
|
|
101
|
-
break
|
|
102
87
|
}
|
|
103
88
|
}
|
|
104
|
-
|
|
105
|
-
private func showPendingState(for contact: Contact) { }
|
|
106
|
-
private func schedulePendingExpiration(for id: UUID) { }
|
|
107
|
-
private func enableMessaging(for contact: Contact) { }
|
|
108
|
-
private func enableCommunication(for handle: CommunicationHandle) { }
|
|
109
|
-
private func showDeniedState(for handle: CommunicationHandle) { }
|
|
110
|
-
private func showFeatureUnavailable() { }
|
|
111
|
-
private func showError(_ error: Error) { }
|
|
112
89
|
}
|
|
113
90
|
```
|
|
114
91
|
|
|
115
|
-
|
|
92
|
+
`avatarImage` takes a `CGImage`, so convert from `UIImage` with `.cgImage`
|
|
93
|
+
before building the person.
|
|
116
94
|
|
|
117
|
-
|
|
95
|
+
## Response manager
|
|
118
96
|
|
|
119
|
-
|
|
120
|
-
|
|
97
|
+
A single observable object keeps approval state for the whole app, keyed by
|
|
98
|
+
handle value, plus the ids of questions still waiting.
|
|
121
99
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
var approvedHandles: Set<String> = []
|
|
128
|
-
var deniedHandles: Set<String> = []
|
|
129
|
-
var pendingHandleValues: Set<String> = []
|
|
130
|
-
var pendingQuestionIDs: Set<UUID> = []
|
|
131
|
-
|
|
132
|
-
private var observerTask: Task<Void, Never>?
|
|
133
|
-
|
|
134
|
-
private init() {
|
|
135
|
-
startObserving()
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
deinit {
|
|
139
|
-
observerTask?.cancel()
|
|
140
|
-
}
|
|
100
|
+
```swift
|
|
101
|
+
import Observation
|
|
102
|
+
@preconcurrency import PermissionKit
|
|
103
|
+
import UIKit
|
|
141
104
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
105
|
+
@available(iOS 26.2, *)
|
|
106
|
+
@Observable @MainActor
|
|
107
|
+
final class ContactApprovals {
|
|
108
|
+
static let shared = ContactApprovals()
|
|
109
|
+
|
|
110
|
+
private(set) var approved: Set<String> = []
|
|
111
|
+
private(set) var denied: Set<String> = []
|
|
112
|
+
private(set) var waitingValues: Set<String> = []
|
|
113
|
+
private(set) var waitingQuestions: Set<UUID> = []
|
|
114
|
+
|
|
115
|
+
func request(_ handles: [CommunicationHandle],
|
|
116
|
+
actions: Set<CommunicationTopic.Action>,
|
|
117
|
+
in host: UIViewController) async throws {
|
|
118
|
+
let people = handles.map {
|
|
119
|
+
CommunicationTopic.PersonInformation(handle: $0, nameComponents: nil, avatarImage: nil)
|
|
153
120
|
}
|
|
154
|
-
|
|
155
|
-
let topic = CommunicationTopic(
|
|
156
|
-
personInformation: personInfo,
|
|
157
|
-
actions: actions
|
|
158
|
-
)
|
|
159
|
-
|
|
160
121
|
let question = PermissionQuestion<CommunicationTopic>(
|
|
161
|
-
communicationTopic:
|
|
122
|
+
communicationTopic: CommunicationTopic(personInformation: people, actions: actions)
|
|
162
123
|
)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
pendingHandleValues.formUnion(handleValues)
|
|
168
|
-
schedulePendingExpiration(
|
|
169
|
-
for: question.id,
|
|
170
|
-
handleValues: handleValues
|
|
171
|
-
)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
func isApproved(_ handleValue: String) -> Bool {
|
|
175
|
-
approvedHandles.contains(handleValue)
|
|
124
|
+
try await AskCenter.shared.ask(question, in: host as UIViewController)
|
|
125
|
+
waitingQuestions.insert(question.id)
|
|
126
|
+
waitingValues.formUnion(handles.map(\.value))
|
|
127
|
+
scheduleExpiry(question.id, values: handles.map(\.value))
|
|
176
128
|
}
|
|
177
129
|
|
|
178
|
-
func
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
func isPending(_ handleValue: String) -> Bool {
|
|
183
|
-
pendingHandleValues.contains(handleValue)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
private func startObserving() {
|
|
187
|
-
observerTask = Task { [weak self] in
|
|
188
|
-
let responses = AskCenter.shared.responses(
|
|
189
|
-
for: CommunicationTopic.self
|
|
190
|
-
)
|
|
191
|
-
for await response in responses {
|
|
192
|
-
await MainActor.run {
|
|
193
|
-
self?.processResponse(response)
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
private func processResponse(
|
|
200
|
-
_ response: PermissionResponse<CommunicationTopic>
|
|
201
|
-
) {
|
|
202
|
-
pendingQuestionIDs.remove(response.question.id)
|
|
203
|
-
|
|
204
|
-
let handleValues = response.question.topic.personInformation
|
|
205
|
-
.map(\.handle.value)
|
|
206
|
-
for value in handleValues {
|
|
207
|
-
pendingHandleValues.remove(value)
|
|
208
|
-
}
|
|
130
|
+
func isApproved(_ value: String) -> Bool { approved.contains(value) }
|
|
131
|
+
func isDenied(_ value: String) -> Bool { denied.contains(value) }
|
|
132
|
+
func isWaiting(_ value: String) -> Bool { waitingValues.contains(value) }
|
|
209
133
|
|
|
134
|
+
func consume(_ response: PermissionResponse<CommunicationTopic>) {
|
|
135
|
+
let people = response.question.topic.personInformation
|
|
136
|
+
let values = people.map { $0.handle.value }
|
|
137
|
+
waitingQuestions.remove(response.question.id)
|
|
138
|
+
waitingValues.subtract(values)
|
|
210
139
|
switch response.choice.answer {
|
|
211
140
|
case .approval:
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
deniedHandles.remove(value)
|
|
215
|
-
}
|
|
141
|
+
approved.formUnion(values)
|
|
142
|
+
denied.subtract(values)
|
|
216
143
|
case .denial:
|
|
217
|
-
|
|
218
|
-
deniedHandles.insert(value)
|
|
219
|
-
}
|
|
144
|
+
denied.formUnion(values)
|
|
220
145
|
@unknown default:
|
|
221
|
-
|
|
146
|
+
denied.formUnion(values)
|
|
222
147
|
}
|
|
223
148
|
}
|
|
224
149
|
|
|
225
|
-
private func
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
150
|
+
private func scheduleExpiry(_ id: UUID, values: [String]) {
|
|
151
|
+
// Product decision: how long a request may stay open before the
|
|
152
|
+
// child can ask again.
|
|
153
|
+
Task {
|
|
154
|
+
try? await ContinuousClock().sleep(until: .now + .seconds(172_800))
|
|
155
|
+
guard waitingQuestions.remove(id) != nil else { return }
|
|
156
|
+
waitingValues.subtract(values)
|
|
157
|
+
}
|
|
231
158
|
}
|
|
232
159
|
}
|
|
233
160
|
```
|
|
234
161
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
Request permission for multiple contacts in a single question.
|
|
238
|
-
|
|
239
|
-
```swift
|
|
240
|
-
func requestGroupPermission(
|
|
241
|
-
contacts: [Contact],
|
|
242
|
-
in viewController: UIViewController
|
|
243
|
-
) async throws {
|
|
244
|
-
let personInfoList = contacts.map { contact in
|
|
245
|
-
CommunicationTopic.PersonInformation(
|
|
246
|
-
handle: CommunicationHandle(
|
|
247
|
-
value: contact.identifier,
|
|
248
|
-
kind: .custom
|
|
249
|
-
),
|
|
250
|
-
nameComponents: contact.nameComponents,
|
|
251
|
-
avatarImage: contact.avatarCGImage
|
|
252
|
-
)
|
|
253
|
-
}
|
|
162
|
+
The pending window is a product choice. Pick one, then either expire the
|
|
163
|
+
request or offer a retry when it runs out.
|
|
254
164
|
|
|
255
|
-
|
|
256
|
-
personInformation: personInfoList,
|
|
257
|
-
actions: [.message, .audioCall, .videoCall]
|
|
258
|
-
)
|
|
165
|
+
## Several contacts in one question
|
|
259
166
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
167
|
+
One question can cover a whole group. Here the handles are `.custom` values
|
|
168
|
+
built from contact identifiers, and the question allows three kinds of
|
|
169
|
+
contact:
|
|
263
170
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if let expiration = question.expirationDate {
|
|
270
|
-
print("Expires: \(expiration)")
|
|
171
|
+
```swift
|
|
172
|
+
func groupQuestion(for memberIDs: [String]) -> PermissionQuestion<CommunicationTopic> {
|
|
173
|
+
let people = memberIDs.map {
|
|
174
|
+
CommunicationTopic.PersonInformation(handle: CommunicationHandle(value: $0, kind: .custom))
|
|
271
175
|
}
|
|
272
|
-
|
|
273
|
-
|
|
176
|
+
let topic = CommunicationTopic(personInformation: people,
|
|
177
|
+
actions: [.message, .audioCall, .videoCall])
|
|
178
|
+
return PermissionQuestion(communicationTopic: topic)
|
|
274
179
|
}
|
|
180
|
+
|
|
181
|
+
let q = groupQuestion(for: ["member-8", "member-9"])
|
|
182
|
+
print(q.id) // UUID for tracking
|
|
183
|
+
print(q.choices.map(\.title)) // the options the parent sees
|
|
184
|
+
print(q.defaultChoice.title)
|
|
185
|
+
print(q.expirationDate as Any) // nil unless set
|
|
275
186
|
```
|
|
276
187
|
|
|
277
|
-
|
|
188
|
+
A question exposes `id`, `choices` (each with a `title`), `defaultChoice` and
|
|
189
|
+
an optional `expirationDate`.
|
|
278
190
|
|
|
279
|
-
|
|
280
|
-
permission UI. This does not prove communication limits are enabled; still
|
|
281
|
-
handle `AskError.communicationLimitsNotEnabled` when asking. `knownHandles(in:)`
|
|
282
|
-
requires a non-nil, nonempty app bundle identifier.
|
|
191
|
+
## Limits-check view model
|
|
283
192
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
var contacts: [ContactItem] = []
|
|
289
|
-
|
|
290
|
-
struct ContactItem: Identifiable {
|
|
291
|
-
let id: String
|
|
292
|
-
let name: String
|
|
293
|
-
let handle: CommunicationHandle
|
|
294
|
-
var isKnownBySystem: Bool = false
|
|
295
|
-
var needsPermissionPrompt: Bool = false
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
func refreshContactStatus() async {
|
|
299
|
-
guard Bundle.main.bundleIdentifier?.isEmpty == false else { return }
|
|
193
|
+
This view model sorts a contact list into "the system already knows this
|
|
194
|
+
handle" and "we will need to ask". It says nothing about whether limits are
|
|
195
|
+
enabled; that only shows up as `AskError.communicationLimitsNotEnabled` when
|
|
196
|
+
you ask.
|
|
300
197
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
198
|
+
```swift
|
|
199
|
+
import Observation
|
|
200
|
+
@preconcurrency import PermissionKit
|
|
201
|
+
|
|
202
|
+
struct RosterEntry: Identifiable {
|
|
203
|
+
let id: String
|
|
204
|
+
let handle: CommunicationHandle
|
|
205
|
+
var isKnownBySystem = false
|
|
206
|
+
var needsPermissionPrompt = true
|
|
207
|
+
}
|
|
304
208
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
209
|
+
@Observable @MainActor
|
|
210
|
+
final class RosterModel {
|
|
211
|
+
var entries: [RosterEntry] = []
|
|
212
|
+
|
|
213
|
+
func classify() async {
|
|
214
|
+
let appID = Bundle.main.bundleIdentifier ?? ""
|
|
215
|
+
if appID.isEmpty { return }
|
|
216
|
+
let known = await CommunicationLimits.current.knownHandles(in: Set(entries.map(\.handle)))
|
|
217
|
+
for index in entries.indices {
|
|
218
|
+
let isKnown = known.contains(entries[index].handle)
|
|
219
|
+
entries[index].isKnownBySystem = isKnown
|
|
220
|
+
entries[index].needsPermissionPrompt = !isKnown
|
|
310
221
|
}
|
|
311
222
|
}
|
|
312
223
|
}
|
|
313
224
|
```
|
|
314
225
|
|
|
315
|
-
## SwiftUI
|
|
226
|
+
## SwiftUI full flow
|
|
316
227
|
|
|
317
|
-
|
|
228
|
+
A screen that works out its state on appear, then shows either a
|
|
229
|
+
`PermissionButton`, a waiting label or the final result. The manager is the
|
|
230
|
+
`ContactApprovals` type above, injected through the environment.
|
|
318
231
|
|
|
319
232
|
```swift
|
|
320
233
|
import SwiftUI
|
|
321
|
-
import PermissionKit
|
|
322
|
-
|
|
323
|
-
struct ContactDetailView: View {
|
|
324
|
-
let contact: Contact
|
|
325
|
-
@State private var permissionState: PermissionState = .unknown
|
|
326
|
-
@Environment(PermissionManager.self) private var permissionManager
|
|
234
|
+
@preconcurrency import PermissionKit
|
|
327
235
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
236
|
+
@available(iOS 26.2, *)
|
|
237
|
+
struct TeammateAccessView: View {
|
|
238
|
+
enum Phase: Equatable {
|
|
239
|
+
case unknown, checking, needsPermission, pending, approved, denied
|
|
240
|
+
case error(String)
|
|
331
241
|
}
|
|
332
242
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
243
|
+
@Environment(ContactApprovals.self) private var approvals
|
|
244
|
+
let handle: CommunicationHandle
|
|
245
|
+
let displayName: PersonNameComponents
|
|
246
|
+
@State private var phase: Phase = .unknown
|
|
337
247
|
|
|
338
|
-
|
|
248
|
+
var body: some View {
|
|
249
|
+
Group {
|
|
250
|
+
switch phase {
|
|
339
251
|
case .unknown, .checking:
|
|
340
|
-
ProgressView(
|
|
341
|
-
|
|
252
|
+
ProgressView()
|
|
342
253
|
case .needsPermission:
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
)
|
|
347
|
-
let question = PermissionQuestion<CommunicationTopic>(
|
|
348
|
-
handle: handle
|
|
349
|
-
)
|
|
350
|
-
|
|
351
|
-
VStack {
|
|
352
|
-
Text("Permission needed to message this contact.")
|
|
353
|
-
.foregroundStyle(.secondary)
|
|
254
|
+
VStack(spacing: 12) {
|
|
255
|
+
Text("A parent or guardian needs to allow chatting with this teammate.")
|
|
256
|
+
.multilineTextAlignment(.center)
|
|
354
257
|
PermissionButton(question: question) {
|
|
355
|
-
Label("Ask
|
|
258
|
+
Label("Ask a parent", systemImage: "figure.and.child.holdinghands")
|
|
356
259
|
}
|
|
357
260
|
.buttonStyle(.borderedProminent)
|
|
358
261
|
}
|
|
359
|
-
|
|
360
262
|
case .pending:
|
|
361
|
-
Label("Waiting for parent
|
|
362
|
-
.foregroundStyle(.secondary)
|
|
363
|
-
|
|
263
|
+
Label("Waiting for a parent", systemImage: "hourglass")
|
|
364
264
|
case .approved:
|
|
365
|
-
Label("
|
|
366
|
-
.foregroundStyle(.green)
|
|
367
|
-
|
|
265
|
+
Label("Chat allowed", systemImage: "checkmark.circle")
|
|
368
266
|
case .denied:
|
|
369
|
-
Label("
|
|
370
|
-
.foregroundStyle(.red)
|
|
371
|
-
|
|
267
|
+
Label("Chat not allowed", systemImage: "xmark.circle")
|
|
372
268
|
case .error(let message):
|
|
373
269
|
Label(message, systemImage: "exclamationmark.triangle")
|
|
374
|
-
.foregroundStyle(.orange)
|
|
375
270
|
}
|
|
376
271
|
}
|
|
377
|
-
.task {
|
|
378
|
-
await checkPermission()
|
|
379
|
-
}
|
|
272
|
+
.task { await resolvePhase() }
|
|
380
273
|
}
|
|
381
274
|
|
|
382
|
-
private
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
permissionState = .pending
|
|
275
|
+
private var question: PermissionQuestion<CommunicationTopic> {
|
|
276
|
+
let person = CommunicationTopic.PersonInformation(handle: handle, nameComponents: displayName)
|
|
277
|
+
return PermissionQuestion(communicationTopic: CommunicationTopic(personInformation: [person],
|
|
278
|
+
actions: [.chat]))
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private func resolvePhase() async {
|
|
282
|
+
phase = .checking
|
|
283
|
+
if await CommunicationLimits.current.isKnownHandle(handle) {
|
|
284
|
+
phase = .approved
|
|
285
|
+
} else if approvals.isApproved(handle.value) {
|
|
286
|
+
phase = .approved
|
|
287
|
+
} else if approvals.isDenied(handle.value) {
|
|
288
|
+
phase = .denied
|
|
289
|
+
} else if approvals.isWaiting(handle.value) {
|
|
290
|
+
phase = .pending
|
|
399
291
|
} else {
|
|
400
|
-
|
|
292
|
+
phase = .needsPermission
|
|
401
293
|
}
|
|
402
294
|
}
|
|
403
295
|
}
|
|
404
296
|
```
|
|
405
297
|
|
|
406
|
-
## macOS
|
|
298
|
+
## macOS
|
|
407
299
|
|
|
408
|
-
On macOS 26.2
|
|
300
|
+
On macOS 26.2 and later the ask call takes an `NSWindow` rather than a view
|
|
301
|
+
controller:
|
|
409
302
|
|
|
410
303
|
```swift
|
|
411
304
|
#if os(macOS)
|
|
412
305
|
import AppKit
|
|
413
|
-
import PermissionKit
|
|
306
|
+
@preconcurrency import PermissionKit
|
|
414
307
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
try await AskCenter.shared.ask(question, in: window)
|
|
308
|
+
@available(macOS 26.2, *)
|
|
309
|
+
@MainActor
|
|
310
|
+
func ask(_ question: PermissionQuestion<CommunicationTopic>, from window: NSWindow) async throws {
|
|
311
|
+
try await AskCenter.shared.ask(question, in: window as NSWindow)
|
|
420
312
|
}
|
|
421
313
|
#endif
|
|
422
314
|
```
|
|
423
315
|
|
|
424
|
-
## Error
|
|
316
|
+
## Error recovery
|
|
425
317
|
|
|
426
|
-
|
|
318
|
+
Map every `AskError` case to something the user can read and, where it makes
|
|
319
|
+
sense, act on.
|
|
427
320
|
|
|
428
321
|
```swift
|
|
429
|
-
|
|
322
|
+
@preconcurrency import PermissionKit
|
|
323
|
+
|
|
324
|
+
struct RecoveryAdvice {
|
|
325
|
+
let title: String
|
|
326
|
+
let message: String
|
|
327
|
+
let action: Action?
|
|
328
|
+
enum Action { case openSettings, retry }
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
@available(iOS 26.2, *)
|
|
332
|
+
func advice(for error: AskError) -> RecoveryAdvice {
|
|
430
333
|
switch error {
|
|
431
334
|
case .communicationLimitsNotEnabled:
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
"Communication limits are not enabled. You can communicate freely.",
|
|
435
|
-
nil
|
|
436
|
-
)
|
|
335
|
+
RecoveryAdvice(title: "No approval needed",
|
|
336
|
+
message: "You can start chatting right away.", action: nil)
|
|
437
337
|
case .contactSyncNotSetup:
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
{ openContactSyncSettings() }
|
|
442
|
-
)
|
|
338
|
+
RecoveryAdvice(title: "Contacts not synced",
|
|
339
|
+
message: "Turn on contact sync so a parent can review this request.",
|
|
340
|
+
action: .openSettings)
|
|
443
341
|
case .invalidQuestion:
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
"The permission request could not be created. Please try again.",
|
|
447
|
-
nil
|
|
448
|
-
)
|
|
342
|
+
RecoveryAdvice(title: "Request could not be built",
|
|
343
|
+
message: "Please try again.", action: .retry)
|
|
449
344
|
case .notAvailable:
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
"This feature is not available on this device.",
|
|
453
|
-
nil
|
|
454
|
-
)
|
|
345
|
+
RecoveryAdvice(title: "Not available here",
|
|
346
|
+
message: "This device cannot send approval requests.", action: nil)
|
|
455
347
|
case .systemError(let underlying):
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
underlying.localizedDescription,
|
|
459
|
-
nil
|
|
460
|
-
)
|
|
348
|
+
RecoveryAdvice(title: "Something went wrong",
|
|
349
|
+
message: underlying.localizedDescription, action: .retry)
|
|
461
350
|
case .unknown:
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
"An unexpected error occurred. Please try again later.",
|
|
465
|
-
nil
|
|
466
|
-
)
|
|
351
|
+
RecoveryAdvice(title: "Request failed",
|
|
352
|
+
message: "Please try again later.", action: .retry)
|
|
467
353
|
@unknown default:
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
"An error occurred.",
|
|
471
|
-
nil
|
|
472
|
-
)
|
|
354
|
+
RecoveryAdvice(title: "Request failed",
|
|
355
|
+
message: "Please try again later.", action: .retry)
|
|
473
356
|
}
|
|
474
357
|
}
|
|
475
358
|
```
|
|
359
|
+
|
|
360
|
+
`.systemError` is deprecated in the 26.2 SDK (the underlying error is now
|
|
361
|
+
thrown on its own), so pair this mapping with a plain `catch` for non-`AskError`
|
|
362
|
+
failures.
|