@mmerterden/multi-agent-pipeline 20.36.0 → 20.37.1

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.
Files changed (222) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/docs/facts.json +2 -2
  3. package/docs/features.md +1 -1
  4. package/docs/recovery-guide.md +1 -1
  5. package/install/catalog-history.json +1 -1
  6. package/manifest.json +225 -148
  7. package/package.json +1 -1
  8. package/pipeline/commands/multi-agent/SKILL.md +33 -154
  9. package/pipeline/commands/multi-agent/analysis/SKILL.md +14 -68
  10. package/pipeline/commands/multi-agent/analysis-jira/SKILL.md +5 -8
  11. package/pipeline/commands/multi-agent/autopilot-on/SKILL.md +38 -109
  12. package/pipeline/commands/multi-agent/channels/SKILL.md +38 -167
  13. package/pipeline/commands/multi-agent/complaint-analysis/SKILL.md +37 -144
  14. package/pipeline/commands/multi-agent/design-check/SKILL.md +27 -131
  15. package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +22 -48
  16. package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +37 -180
  17. package/pipeline/commands/multi-agent/jira/SKILL.md +16 -46
  18. package/pipeline/commands/multi-agent/manual-test/SKILL.md +12 -7
  19. package/pipeline/commands/multi-agent/refactor/SKILL.md +23 -184
  20. package/pipeline/commands/multi-agent/resume/SKILL.md +30 -136
  21. package/pipeline/commands/multi-agent/review/SKILL.md +24 -199
  22. package/pipeline/commands/multi-agent/setup/SKILL.md +25 -181
  23. package/pipeline/commands/multi-agent/stack/SKILL.md +9 -53
  24. package/pipeline/commands/multi-agent/status/SKILL.md +13 -2
  25. package/pipeline/commands/multi-agent/store-ready/SKILL.md +27 -226
  26. package/pipeline/commands/multi-agent/sync/SKILL.md +35 -164
  27. package/pipeline/commands/multi-agent/test-gen/SKILL.md +2 -4
  28. package/pipeline/commands/multi-agent/update/SKILL.md +18 -60
  29. package/pipeline/contract/CHANGELOG.md +9 -0
  30. package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
  31. package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
  32. package/pipeline/contract/fixtures/error-unsigned.json +1 -1
  33. package/pipeline/contract/fixtures/runs-awaiting-question.json +1 -1
  34. package/pipeline/contract/fixtures/runs-empty.json +1 -1
  35. package/pipeline/contract/fixtures/runs-failed.json +1 -1
  36. package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +1 -1
  37. package/pipeline/contract/fixtures/runs-pr-opened.json +1 -1
  38. package/pipeline/contract/fixtures/runs-running.json +1 -1
  39. package/pipeline/contract/manifest.json +5 -5
  40. package/pipeline/contract/types/index.d.ts +3 -2
  41. package/pipeline/multi-agent-refs/audit-guide.md +20 -0
  42. package/pipeline/multi-agent-refs/autopilot-on/details.md +148 -0
  43. package/pipeline/multi-agent-refs/channels/generator.md +14 -0
  44. package/pipeline/multi-agent-refs/channels/preview.md +13 -0
  45. package/pipeline/multi-agent-refs/channels/reference.md +46 -0
  46. package/pipeline/multi-agent-refs/complaint-analysis/phases.md +159 -0
  47. package/pipeline/multi-agent-refs/design-check/export.md +1 -0
  48. package/pipeline/multi-agent-refs/design-check/init-inventory.md +1 -1
  49. package/pipeline/multi-agent-refs/design-check/scope-and-ledger.md +64 -0
  50. package/pipeline/multi-agent-refs/features/analysis-command-notes.md +53 -0
  51. package/pipeline/multi-agent-refs/features/commit-multi-repo.md +63 -0
  52. package/pipeline/multi-agent-refs/features/dev-multi-repo.md +51 -0
  53. package/pipeline/multi-agent-refs/features/test-gap.md +40 -0
  54. package/pipeline/multi-agent-refs/features/worktree-finalize.md +1 -1
  55. package/pipeline/multi-agent-refs/garbage-collect/phases.md +60 -0
  56. package/pipeline/multi-agent-refs/ios-coding-standard/plan.md +71 -0
  57. package/pipeline/multi-agent-refs/ios-coding-standard/resolve-and-scan.md +135 -0
  58. package/pipeline/multi-agent-refs/jira/picker-detail.md +71 -0
  59. package/pipeline/multi-agent-refs/orchestrator/copilot-pipeline.md +88 -0
  60. package/pipeline/multi-agent-refs/orchestrator/dispatcher.md +96 -1
  61. package/pipeline/multi-agent-refs/orchestrator/phase-3-user-test.md +12 -9
  62. package/pipeline/multi-agent-refs/phases/phase-1-plan.md +4 -4
  63. package/pipeline/multi-agent-refs/phases/phase-2-dev.md +8 -47
  64. package/pipeline/multi-agent-refs/phases/phase-3-review.md +21 -65
  65. package/pipeline/multi-agent-refs/phases/phase-4-commit.md +9 -59
  66. package/pipeline/multi-agent-refs/phases/phase-5-report.md +0 -31
  67. package/pipeline/multi-agent-refs/refactor/sweep.md +190 -0
  68. package/pipeline/multi-agent-refs/resume/details.md +115 -2
  69. package/pipeline/multi-agent-refs/review/inputs.md +33 -1
  70. package/pipeline/multi-agent-refs/review/module-guides.md +9 -1
  71. package/pipeline/multi-agent-refs/review/panel.md +75 -0
  72. package/pipeline/multi-agent-refs/review/valven.md +5 -1
  73. package/pipeline/multi-agent-refs/rules.md +1 -1
  74. package/pipeline/multi-agent-refs/setup/hooks-and-plugins.md +4 -0
  75. package/pipeline/multi-agent-refs/setup/token-save-flow.md +13 -0
  76. package/pipeline/multi-agent-refs/stack/background.md +72 -0
  77. package/pipeline/multi-agent-refs/store-ready/gates.md +139 -0
  78. package/pipeline/multi-agent-refs/store-ready/report.md +40 -2
  79. package/pipeline/multi-agent-refs/sync/office.md +10 -0
  80. package/pipeline/multi-agent-refs/sync/overview.md +61 -0
  81. package/pipeline/multi-agent-refs/sync/repo.md +91 -0
  82. package/pipeline/multi-agent-refs/update/details.md +83 -0
  83. package/pipeline/office/index.html +6 -3
  84. package/pipeline/office/toolkit.json +1 -1
  85. package/pipeline/rules/figma-pipeline.md +6 -3
  86. package/pipeline/schemas/prefs.schema.json +1 -1
  87. package/pipeline/schemas/runs-index.schema.json +2 -1
  88. package/pipeline/scripts/_memory-policy.mjs +27 -5
  89. package/pipeline/scripts/checkpoint-fold.sh +59 -0
  90. package/pipeline/scripts/contract-server.mjs +1 -1
  91. package/pipeline/scripts/gate-ledger.mjs +1 -1
  92. package/pipeline/scripts/office-control.mjs +41 -15
  93. package/pipeline/scripts/offload-ref.sh +1 -1
  94. package/pipeline/scripts/rework-rulings.mjs +5 -3
  95. package/pipeline/scripts/runs-index.mjs +34 -7
  96. package/pipeline/scripts/scope-check-gate.mjs +15 -2
  97. package/pipeline/scripts/status-overview.mjs +401 -0
  98. package/pipeline/scripts/user-test-handoff.sh +203 -0
  99. package/pipeline/scripts/validate-planning.mjs +5 -3
  100. package/pipeline/scripts/validate-triage.mjs +2 -1
  101. package/pipeline/scripts/write-state.mjs +13 -4
  102. package/pipeline/skills/.skill-manifest.json +64 -64
  103. package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +7 -70
  104. package/pipeline/skills/shared/core/apple-archive-compliance/references/examples.md +67 -0
  105. package/pipeline/skills/shared/core/apple-archive-compliance/references/version-history.md +8 -0
  106. package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +10 -72
  107. package/pipeline/skills/shared/core/google-play-compliance/references/examples.md +69 -0
  108. package/pipeline/skills/shared/core/google-play-compliance/references/notes.md +7 -0
  109. package/pipeline/skills/shared/core/google-play-compliance/references/version-history.md +5 -0
  110. package/pipeline/skills/shared/core/multi-agent/SKILL.md +39 -262
  111. package/pipeline/skills/shared/core/multi-agent-autopilot-on/SKILL.md +35 -73
  112. package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +37 -337
  113. package/pipeline/skills/shared/core/multi-agent-design-check/SKILL.md +27 -122
  114. package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +8 -340
  115. package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +37 -180
  116. package/pipeline/skills/shared/core/multi-agent-manual-test/SKILL.md +12 -7
  117. package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +23 -184
  118. package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +14 -82
  119. package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +25 -277
  120. package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +13 -2
  121. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +73 -217
  122. package/pipeline/skills/shared/external/alarmkit/SKILL.md +11 -162
  123. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-code.md +257 -0
  124. package/pipeline/skills/shared/external/android-architecture/SKILL.md +7 -58
  125. package/pipeline/skills/shared/external/android-architecture/references/code-examples.md +68 -0
  126. package/pipeline/skills/shared/external/app-intents/SKILL.md +3 -45
  127. package/pipeline/skills/shared/external/app-intents/references/code-examples.md +54 -0
  128. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +5 -81
  129. package/pipeline/skills/shared/external/app-store-optimization/references/engagement-and-localization.md +96 -0
  130. package/pipeline/skills/shared/external/app-store-review/SKILL.md +6 -112
  131. package/pipeline/skills/shared/external/app-store-review/references/monetization-and-submission.md +138 -0
  132. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +10 -117
  133. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models-code.md +186 -0
  134. package/pipeline/skills/shared/external/authentication/SKILL.md +3 -50
  135. package/pipeline/skills/shared/external/authentication/references/code-examples.md +59 -0
  136. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +3 -84
  137. package/pipeline/skills/shared/external/callkit-voip/references/code-examples.md +93 -0
  138. package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +7 -263
  139. package/pipeline/skills/shared/external/ci-cd-pipelines/references/pipeline-examples.md +281 -0
  140. package/pipeline/skills/shared/external/clean-code/SKILL.md +8 -41
  141. package/pipeline/skills/shared/external/clean-code/references/code-examples.md +56 -0
  142. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +8 -146
  143. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-code.md +214 -0
  144. package/pipeline/skills/shared/external/compose-components/SKILL.md +6 -80
  145. package/pipeline/skills/shared/external/compose-components/references/code-examples.md +89 -0
  146. package/pipeline/skills/shared/external/compose-navigation/SKILL.md +5 -64
  147. package/pipeline/skills/shared/external/compose-navigation/references/code-examples.md +70 -0
  148. package/pipeline/skills/shared/external/core-data/SKILL.md +9 -157
  149. package/pipeline/skills/shared/external/core-data/references/core-data-code.md +221 -0
  150. package/pipeline/skills/shared/external/core-nfc/SKILL.md +3 -89
  151. package/pipeline/skills/shared/external/core-nfc/references/code-examples.md +98 -0
  152. package/pipeline/skills/shared/external/coreml/SKILL.md +17 -133
  153. package/pipeline/skills/shared/external/coreml/references/coreml-code.md +208 -0
  154. package/pipeline/skills/shared/external/css-modern/SKILL.md +8 -131
  155. package/pipeline/skills/shared/external/css-modern/references/code-examples.md +142 -0
  156. package/pipeline/skills/shared/external/database-patterns/SKILL.md +6 -103
  157. package/pipeline/skills/shared/external/database-patterns/references/orm-examples.md +112 -0
  158. package/pipeline/skills/shared/external/html-semantic/SKILL.md +7 -48
  159. package/pipeline/skills/shared/external/html-semantic/references/code-examples.md +54 -0
  160. package/pipeline/skills/shared/external/humanizer/SKILL.md +6 -19
  161. package/pipeline/skills/shared/external/humanizer/references/voice.md +24 -0
  162. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +13 -170
  163. package/pipeline/skills/shared/external/ios-accessibility/references/assistive-tech-review.md +224 -0
  164. package/pipeline/skills/shared/external/ios-localization/SKILL.md +2 -24
  165. package/pipeline/skills/shared/external/ios-localization/references/code-examples.md +30 -0
  166. package/pipeline/skills/shared/external/ios-networking/SKILL.md +3 -58
  167. package/pipeline/skills/shared/external/ios-networking/references/code-examples.md +67 -0
  168. package/pipeline/skills/shared/external/ios-security/SKILL.md +4 -64
  169. package/pipeline/skills/shared/external/ios-security/references/code-examples.md +74 -0
  170. package/pipeline/skills/shared/external/live-activities/SKILL.md +4 -90
  171. package/pipeline/skills/shared/external/live-activities/references/code-examples.md +108 -0
  172. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +9 -23
  173. package/pipeline/skills/shared/external/localization-reuse-map/references/project-settings.md +22 -0
  174. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +5 -95
  175. package/pipeline/skills/shared/external/mapkit-location/references/code-examples.md +117 -0
  176. package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +7 -106
  177. package/pipeline/skills/shared/external/nextjs-app-router/references/app-router-examples.md +124 -0
  178. package/pipeline/skills/shared/external/permissionkit/SKILL.md +2 -31
  179. package/pipeline/skills/shared/external/permissionkit/references/code-examples.md +37 -0
  180. package/pipeline/skills/shared/external/play-store-review/SKILL.md +6 -74
  181. package/pipeline/skills/shared/external/play-store-review/references/signing-and-policies.md +83 -0
  182. package/pipeline/skills/shared/external/push-notifications/SKILL.md +8 -125
  183. package/pipeline/skills/shared/external/push-notifications/references/notification-code.md +176 -0
  184. package/pipeline/skills/shared/external/python-patterns/SKILL.md +7 -158
  185. package/pipeline/skills/shared/external/python-patterns/references/python-examples.md +176 -0
  186. package/pipeline/skills/shared/external/rest-api-design/SKILL.md +7 -157
  187. package/pipeline/skills/shared/external/rest-api-design/references/api-examples.md +175 -0
  188. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +11 -230
  189. package/pipeline/skills/shared/external/swift-architecture/references/pattern-examples.md +295 -0
  190. package/pipeline/skills/shared/external/swift-codable/SKILL.md +17 -149
  191. package/pipeline/skills/shared/external/swift-codable/references/codable-recipes.md +207 -0
  192. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +12 -99
  193. package/pipeline/skills/shared/external/swift-concurrency/references/code-examples.md +100 -0
  194. package/pipeline/skills/shared/external/swift-concurrency/references/swift-6-2-additions.md +18 -0
  195. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +9 -78
  196. package/pipeline/skills/shared/external/swift-formatstyle/references/formatstyle-examples.md +113 -0
  197. package/pipeline/skills/shared/external/swift-language/SKILL.md +3 -56
  198. package/pipeline/skills/shared/external/swift-language/references/code-examples.md +65 -0
  199. package/pipeline/skills/shared/external/swift-testing-pro/SKILL.md +1 -0
  200. package/pipeline/skills/shared/external/swiftdata/SKILL.md +6 -122
  201. package/pipeline/skills/shared/external/swiftdata/references/code-examples.md +140 -0
  202. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +12 -139
  203. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/interop-code.md +198 -0
  204. package/pipeline/skills/shared/external/tailwind-css/SKILL.md +9 -81
  205. package/pipeline/skills/shared/external/tailwind-css/references/code-examples.md +91 -0
  206. package/pipeline/skills/shared/external/testing-backend/SKILL.md +7 -207
  207. package/pipeline/skills/shared/external/testing-backend/references/testing-examples.md +225 -0
  208. package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +8 -89
  209. package/pipeline/skills/shared/external/typescript-patterns/references/code-examples.md +100 -0
  210. package/pipeline/skills/shared/external/vision-framework/SKILL.md +2 -20
  211. package/pipeline/skills/shared/external/vision-framework/references/code-examples.md +26 -0
  212. package/pipeline/skills/shared/external/vue-composition/SKILL.md +7 -169
  213. package/pipeline/skills/shared/external/vue-composition/references/vue-examples.md +187 -0
  214. package/pipeline/skills/shared/external/web-accessibility/SKILL.md +7 -174
  215. package/pipeline/skills/shared/external/web-accessibility/references/a11y-examples.md +192 -0
  216. package/pipeline/skills/shared/external/web-performance/SKILL.md +8 -66
  217. package/pipeline/skills/shared/external/web-performance/references/code-examples.md +77 -0
  218. package/pipeline/skills/shared/external/web-testing/SKILL.md +11 -186
  219. package/pipeline/skills/shared/external/web-testing/references/testing-examples.md +213 -0
  220. /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/format-and-output.md +0 -0
  221. /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/publish-and-snapshot.md +0 -0
  222. /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/sources-and-recipes.md +0 -0
@@ -0,0 +1,67 @@
1
+ # iOS Networking Code Examples
2
+
3
+ Full code samples for the SKILL.md sections that link here. The rules and the decision guidance stay in SKILL.md; each heading below matches the SKILL.md section the code belongs to.
4
+
5
+ ## Protocol-based client
6
+
7
+ ```swift
8
+ protocol APIClientProtocol: Sendable {
9
+ func fetch<Reply: Decodable & Sendable>(_ endpoint: Endpoint, as kind: Reply.Type) async throws -> Reply
10
+ func send<Reply: Decodable & Sendable>(_ endpoint: Endpoint, json payload: some Encodable & Sendable,
11
+ as kind: Reply.Type) async throws -> Reply
12
+ func perform(_ endpoint: Endpoint) async throws
13
+ func upload<Reply: Decodable & Sendable>(_ endpoint: Endpoint, bytes: Data,
14
+ as kind: Reply.Type) async throws -> Reply
15
+ }
16
+
17
+ struct Endpoint: Sendable {
18
+ enum HTTPMethod: String, Sendable { case get, post, put, patch, delete }
19
+
20
+ var path: String
21
+ var method: HTTPMethod = .get
22
+ var queryItems: [URLQueryItem] = []
23
+ var headers: [String: String] = [:]
24
+ var body: Data? = nil
25
+ var cachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy
26
+ var timeoutInterval: TimeInterval = 30
27
+
28
+ func urlRequest(relativeTo baseURL: URL) throws -> URLRequest {
29
+ guard var parts = URLComponents(url: baseURL.appending(path: path),
30
+ resolvingAgainstBaseURL: true) else {
31
+ throw NetworkError.invalidURL
32
+ }
33
+ if !queryItems.isEmpty { parts.queryItems = queryItems }
34
+ guard let url = parts.url else { throw NetworkError.invalidURL }
35
+ var request = URLRequest(url: url, cachePolicy: cachePolicy, timeoutInterval: timeoutInterval)
36
+ request.httpMethod = method.rawValue.uppercased()
37
+ request.httpBody = body
38
+ request.allHTTPHeaderFields = headers
39
+ return request
40
+ }
41
+ }
42
+ ```
43
+
44
+ ## Retry with exponential backoff
45
+
46
+ ```swift
47
+ func withRetry<T: Sendable>(
48
+ maxAttempts: Int = 3,
49
+ initialDelay: Duration = .seconds(1),
50
+ maxDelay: Duration = .seconds(30),
51
+ shouldRetry: @Sendable (any Error) -> Bool = isTransient,
52
+ operation: @Sendable () async throws -> T
53
+ ) async throws -> T {
54
+ precondition(maxAttempts > 0, "maxAttempts must be at least 1")
55
+ for attempt in 0..<maxAttempts {
56
+ try Task.checkCancellation()
57
+ do {
58
+ return try await operation()
59
+ } catch {
60
+ guard attempt < maxAttempts - 1, shouldRetry(error) else { throw error }
61
+ let capped = min(initialDelay * (1 << attempt), maxDelay)
62
+ try await Task.sleep(for: capped + capped * Double.random(in: 0...0.1))
63
+ }
64
+ }
65
+ preconditionFailure("the last attempt always returns or throws")
66
+ }
67
+ ```
@@ -32,34 +32,9 @@ passwords, tokens, API keys, or secrets in UserDefaults, files, or Core Data.
32
32
 
33
33
  ### Storing Credentials
34
34
 
35
- ```swift
36
- func saveToKeychain(account: String, data: Data, service: String) throws {
37
- let query: [String: Any] = [
38
- kSecClass as String: kSecClassGenericPassword,
39
- kSecAttrAccount as String: account,
40
- kSecAttrService as String: service,
41
- kSecValueData as String: data,
42
- kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
43
- ]
35
+ Save with `SecItemAdd` and an explicit accessibility class; when the item already exists (`errSecDuplicateItem`), update it with `SecItemUpdate` instead of failing.
44
36
 
45
- let status = SecItemAdd(query as CFDictionary, nil)
46
-
47
- if status == errSecDuplicateItem {
48
- let updateQuery: [String: Any] = [
49
- kSecClass as String: kSecClassGenericPassword,
50
- kSecAttrAccount as String: account,
51
- kSecAttrService as String: service
52
- ]
53
- let updates: [String: Any] = [kSecValueData as String: data]
54
- let updateStatus = SecItemUpdate(updateQuery as CFDictionary, updates as CFDictionary)
55
- guard updateStatus == errSecSuccess else {
56
- throw KeychainError.updateFailed(updateStatus)
57
- }
58
- } else if status != errSecSuccess {
59
- throw KeychainError.saveFailed(status)
60
- }
61
- }
62
- ```
37
+ Code: [Storing Credentials](references/code-examples.md#storing-credentials).
63
38
 
64
39
  ### Reading Credentials
65
40
 
@@ -364,43 +339,7 @@ Pin certificates for sensitive API connections to prevent MITM attacks.
364
339
 
365
340
  ### URLSession Delegate Pinning
366
341
 
367
- ```swift
368
- import CryptoKit
369
-
370
- class PinnedSessionDelegate: NSObject, URLSessionDelegate {
371
- // SHA-256 hash of the certificate's Subject Public Key Info
372
- private let pinnedHashes: Set<String> = [
373
- "base64EncodedSHA256HashOfSPKI=="
374
- ]
375
-
376
- func urlSession(
377
- _ session: URLSession,
378
- didReceive challenge: URLAuthenticationChallenge
379
- ) async -> (URLSession.AuthChallengeDisposition, URLCredential?) {
380
- guard let trust = challenge.protectionSpace.serverTrust,
381
- let chain = SecTrustCopyCertificateChain(trust) as? [SecCertificate],
382
- let certificate = chain.first else {
383
- return (.cancelAuthenticationChallenge, nil)
384
- }
385
-
386
- guard let publicKey = SecCertificateCopyKey(certificate),
387
- let publicKeyData = SecKeyCopyExternalRepresentation(
388
- publicKey, nil
389
- ) as Data? else {
390
- return (.cancelAuthenticationChallenge, nil)
391
- }
392
-
393
- let hash = SHA256.hash(data: publicKeyData)
394
- let hashString = Data(hash).base64EncodedString()
395
-
396
- if pinnedHashes.contains(hashString) {
397
- return (.useCredential, URLCredential(trust: trust))
398
- }
399
-
400
- return (.cancelAuthenticationChallenge, nil)
401
- }
402
- }
403
- ```
342
+ Code: [URLSession Delegate Pinning](references/code-examples.md#urlsession-delegate-pinning).
404
343
 
405
344
  Rules:
406
345
  - Pin the public key hash, not the certificate. Certificates rotate; public keys are more stable.
@@ -495,4 +434,5 @@ compliance checklists, see `references/app-review-guidelines.md`.
495
434
  - Privacy manifest guide: `references/privacy-manifest.md`
496
435
  - App Review security guidelines: `references/app-review-guidelines.md`
497
436
  - File storage directory guide and protection: `references/file-storage-patterns.md`
437
+ - [Code examples](references/code-examples.md): open for the full code behind Storing Credentials, URLSession Delegate Pinning.
498
438
 
@@ -0,0 +1,74 @@
1
+ # iOS Security Code Examples
2
+
3
+ Full code samples for the SKILL.md sections that link here. The rules and the decision guidance stay in SKILL.md; each heading below matches the SKILL.md section the code belongs to.
4
+
5
+ ## Storing Credentials
6
+
7
+ ```swift
8
+ func saveToKeychain(account: String, data: Data, service: String) throws {
9
+ let query: [String: Any] = [
10
+ kSecClass as String: kSecClassGenericPassword,
11
+ kSecAttrAccount as String: account,
12
+ kSecAttrService as String: service,
13
+ kSecValueData as String: data,
14
+ kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
15
+ ]
16
+
17
+ let status = SecItemAdd(query as CFDictionary, nil)
18
+
19
+ if status == errSecDuplicateItem {
20
+ let updateQuery: [String: Any] = [
21
+ kSecClass as String: kSecClassGenericPassword,
22
+ kSecAttrAccount as String: account,
23
+ kSecAttrService as String: service
24
+ ]
25
+ let updates: [String: Any] = [kSecValueData as String: data]
26
+ let updateStatus = SecItemUpdate(updateQuery as CFDictionary, updates as CFDictionary)
27
+ guard updateStatus == errSecSuccess else {
28
+ throw KeychainError.updateFailed(updateStatus)
29
+ }
30
+ } else if status != errSecSuccess {
31
+ throw KeychainError.saveFailed(status)
32
+ }
33
+ }
34
+ ```
35
+
36
+ ## URLSession Delegate Pinning
37
+
38
+ ```swift
39
+ import CryptoKit
40
+
41
+ class PinnedSessionDelegate: NSObject, URLSessionDelegate {
42
+ // SHA-256 hash of the certificate's Subject Public Key Info
43
+ private let pinnedHashes: Set<String> = [
44
+ "base64EncodedSHA256HashOfSPKI=="
45
+ ]
46
+
47
+ func urlSession(
48
+ _ session: URLSession,
49
+ didReceive challenge: URLAuthenticationChallenge
50
+ ) async -> (URLSession.AuthChallengeDisposition, URLCredential?) {
51
+ guard let trust = challenge.protectionSpace.serverTrust,
52
+ let chain = SecTrustCopyCertificateChain(trust) as? [SecCertificate],
53
+ let certificate = chain.first else {
54
+ return (.cancelAuthenticationChallenge, nil)
55
+ }
56
+
57
+ guard let publicKey = SecCertificateCopyKey(certificate),
58
+ let publicKeyData = SecKeyCopyExternalRepresentation(
59
+ publicKey, nil
60
+ ) as Data? else {
61
+ return (.cancelAuthenticationChallenge, nil)
62
+ }
63
+
64
+ let hash = SHA256.hash(data: publicKeyData)
65
+ let hashString = Data(hash).base64EncodedString()
66
+
67
+ if pinnedHashes.contains(hashString) {
68
+ return (.useCredential, URLCredential(trust: trust))
69
+ }
70
+
71
+ return (.cancelAuthenticationChallenge, nil)
72
+ }
73
+ }
74
+ ```
@@ -62,34 +62,7 @@ Two layers, with different lifetimes:
62
62
  - `ContentState` carries whatever changes. The entire value is encoded on
63
63
  every update and in every push, so keep it lean.
64
64
 
65
- ```swift
66
- import ActivityKit
67
-
68
- struct ChargingAttributes: ActivityAttributes {
69
- struct ContentState: Codable, Hashable {
70
- var batteryPercent: Int
71
- var finishWindow: ClosedRange<Date>
72
- var phase: ChargePhase
73
- }
74
-
75
- var stationName: String
76
- var connectorLabel: String
77
- }
78
-
79
- enum ChargePhase: String, Codable, Hashable, CaseIterable {
80
- case queued, connecting, charging, balancing, complete
81
-
82
- var symbolName: String {
83
- switch self {
84
- case .queued: "hourglass"
85
- case .connecting: "powerplug"
86
- case .charging: "bolt.car"
87
- case .balancing: "slider.horizontal.3"
88
- case .complete: "checkmark.circle.fill"
89
- }
90
- }
91
- }
92
- ```
65
+ Code: [Modelling the data](references/code-examples.md#modelling-the-data).
93
66
 
94
67
  ### Stale date
95
68
 
@@ -114,25 +87,7 @@ let snapshot = ActivityContent(
114
87
  `pushType: .token` when a server should be able to update it. The
115
88
  `ActivityContent` form of `request` is iOS 16.2+.
116
89
 
117
- ```swift
118
- func beginCharging(at station: String) {
119
- let attributes = ChargingAttributes(stationName: station, connectorLabel: "B2")
120
- let window = Date.now...Date.now.addingTimeInterval(30 * 60)
121
- let initial = ChargingAttributes.ContentState(
122
- batteryPercent: 18, finishWindow: window, phase: .connecting
123
- )
124
- do {
125
- let activity = try Activity.request(
126
- attributes: attributes,
127
- content: ActivityContent(state: initial, staleDate: nil, relevanceScore: 75),
128
- pushType: .token
129
- )
130
- sessionStore.remember(activity.id)
131
- } catch {
132
- logger.error("Live Activity refused: \(error.localizedDescription)")
133
- }
134
- }
135
- ```
90
+ Code: [Start](references/code-examples.md#start).
136
91
 
137
92
  ### Update
138
93
 
@@ -198,49 +153,7 @@ The Lock Screen is the surface that always exists: every device on iOS 16.1 or
198
153
  later shows Live Activities there. Lay it out first, then squeeze it into the
199
154
  Dynamic Island.
200
155
 
201
- ```swift
202
- import SwiftUI
203
- import WidgetKit
204
-
205
- struct ChargingLiveActivity: Widget {
206
- var body: some WidgetConfiguration {
207
- ActivityConfiguration(for: ChargingAttributes.self) { context in
208
- HStack(spacing: 12) {
209
- Image(systemName: context.state.phase.symbolName)
210
- VStack(alignment: .leading) {
211
- Text(context.attributes.stationName).font(.headline)
212
- Text("\(context.state.batteryPercent)%")
213
- }
214
- Spacer()
215
- if context.isStale {
216
- Label("Refreshing", systemImage: "arrow.trianglehead.2.clockwise")
217
- .foregroundStyle(.secondary)
218
- } else {
219
- Text(timerInterval: context.state.finishWindow, countsDown: true)
220
- .monospacedDigit()
221
- }
222
- }
223
- .padding()
224
- } dynamicIsland: { context in
225
- DynamicIsland {
226
- DynamicIslandExpandedRegion(.center) {
227
- Text(context.attributes.stationName)
228
- }
229
- DynamicIslandExpandedRegion(.trailing) {
230
- Text("\(context.state.batteryPercent)%")
231
- }
232
- } compactLeading: {
233
- Image(systemName: "bolt.car")
234
- } compactTrailing: {
235
- Text(timerInterval: context.state.finishWindow, countsDown: true)
236
- .monospacedDigit()
237
- } minimal: {
238
- Image(systemName: "bolt.car")
239
- }
240
- }
241
- }
242
- }
243
- ```
156
+ Code: [Lock Screen](references/code-examples.md#lock-screen).
244
157
 
245
158
  Static values come from `context.attributes`; changing values from
246
159
  `context.state`.
@@ -447,3 +360,4 @@ update or end activities; they cannot start one. They are addressed with
447
360
  attributes model, start/update/end variants, server payloads and headers, channel pushes, concurrent activities, state and token observation,
448
361
  authorization, errors, background behaviour, testing, Info.plist keys and
449
362
  documentation links.
363
+ - [Code examples](references/code-examples.md): open for the full code behind Modelling the data, Start, Lock Screen.
@@ -0,0 +1,108 @@
1
+ # Live Activities Code Examples
2
+
3
+ <!-- toc -->
4
+ - [Modelling the data](#modelling-the-data)
5
+ - [Start](#start)
6
+ - [Lock Screen](#lock-screen)
7
+ <!-- /toc -->
8
+
9
+ Full code samples for the SKILL.md sections that link here. The rules and the decision guidance stay in SKILL.md; each heading below matches the SKILL.md section the code belongs to.
10
+
11
+ ## Modelling the data
12
+
13
+ ```swift
14
+ import ActivityKit
15
+
16
+ struct ChargingAttributes: ActivityAttributes {
17
+ struct ContentState: Codable, Hashable {
18
+ var batteryPercent: Int
19
+ var finishWindow: ClosedRange<Date>
20
+ var phase: ChargePhase
21
+ }
22
+
23
+ var stationName: String
24
+ var connectorLabel: String
25
+ }
26
+
27
+ enum ChargePhase: String, Codable, Hashable, CaseIterable {
28
+ case queued, connecting, charging, balancing, complete
29
+
30
+ var symbolName: String {
31
+ switch self {
32
+ case .queued: "hourglass"
33
+ case .connecting: "powerplug"
34
+ case .charging: "bolt.car"
35
+ case .balancing: "slider.horizontal.3"
36
+ case .complete: "checkmark.circle.fill"
37
+ }
38
+ }
39
+ }
40
+ ```
41
+
42
+ ## Start
43
+
44
+ ```swift
45
+ func beginCharging(at station: String) {
46
+ let attributes = ChargingAttributes(stationName: station, connectorLabel: "B2")
47
+ let window = Date.now...Date.now.addingTimeInterval(30 * 60)
48
+ let initial = ChargingAttributes.ContentState(
49
+ batteryPercent: 18, finishWindow: window, phase: .connecting
50
+ )
51
+ do {
52
+ let activity = try Activity.request(
53
+ attributes: attributes,
54
+ content: ActivityContent(state: initial, staleDate: nil, relevanceScore: 75),
55
+ pushType: .token
56
+ )
57
+ sessionStore.remember(activity.id)
58
+ } catch {
59
+ logger.error("Live Activity refused: \(error.localizedDescription)")
60
+ }
61
+ }
62
+ ```
63
+
64
+ ## Lock Screen
65
+
66
+ ```swift
67
+ import SwiftUI
68
+ import WidgetKit
69
+
70
+ struct ChargingLiveActivity: Widget {
71
+ var body: some WidgetConfiguration {
72
+ ActivityConfiguration(for: ChargingAttributes.self) { context in
73
+ HStack(spacing: 12) {
74
+ Image(systemName: context.state.phase.symbolName)
75
+ VStack(alignment: .leading) {
76
+ Text(context.attributes.stationName).font(.headline)
77
+ Text("\(context.state.batteryPercent)%")
78
+ }
79
+ Spacer()
80
+ if context.isStale {
81
+ Label("Refreshing", systemImage: "arrow.trianglehead.2.clockwise")
82
+ .foregroundStyle(.secondary)
83
+ } else {
84
+ Text(timerInterval: context.state.finishWindow, countsDown: true)
85
+ .monospacedDigit()
86
+ }
87
+ }
88
+ .padding()
89
+ } dynamicIsland: { context in
90
+ DynamicIsland {
91
+ DynamicIslandExpandedRegion(.center) {
92
+ Text(context.attributes.stationName)
93
+ }
94
+ DynamicIslandExpandedRegion(.trailing) {
95
+ Text("\(context.state.batteryPercent)%")
96
+ }
97
+ } compactLeading: {
98
+ Image(systemName: "bolt.car")
99
+ } compactTrailing: {
100
+ Text(timerInterval: context.state.finishWindow, countsDown: true)
101
+ .monospacedDigit()
102
+ } minimal: {
103
+ Image(systemName: "bolt.car")
104
+ }
105
+ }
106
+ }
107
+ }
108
+ ```
@@ -25,30 +25,15 @@ It is not a key-minting tool: new keys and their values belong to the resource-a
25
25
 
26
26
  ## Project settings
27
27
 
28
- Nothing company-specific is built in. Establish these once per project (read them from the project's docs or ask), then reuse them for every screen:
29
-
30
- | Setting | What it is | Example |
31
- |---|---|---|
32
- | Specs repo + design export | Screen frames, `components-used.md`, `screenshot.png`, `tree.json` | `design-export/<project>/screens/<slug>/` |
33
- | Resources root | Suggested new values and the component registry | `resources/Localization/Suggested/<Key>.json` |
34
- | Legacy label endpoint | For refreshing the legacy snapshot | `--endpoint` + `--headers-file` |
35
- | Legacy key prefix | Display-only prefix the backend stores | mapping `legacyKeyPrefix`, e.g. `App-` |
36
- | Web i18n convention | For legacy and/or new web apps | `t('key')` / `$t('key')`, keys in `locales/<lang>.json` |
37
- | Confluence target | Base URL, space, parent page | `CONFLUENCE_BASE_URL` / `CONFLUENCE_SPACE` / `CONFLUENCE_PARENT` |
38
- | CMS taxonomy | Property Group / Module buckets in the spreadsheet | `--taxonomy cms-taxonomy.json` |
39
- | Document language | Headings, legend and labels | `--ui-lang en` (default) or `tr` |
40
-
41
- Secrets live only in a keychain item or a `--headers-file`. They never go into the mapping, the taxonomy file, the docs or a command line.
42
-
43
- At the start, ask the user only for what is still unknown: where the implemented screen lives, the Figma file key and node-ids (or a confirmed design-export slug), and, once it is time to publish, the Confluence target and the formats wanted.
28
+ Nothing company-specific is built in. Establish these once per project and reuse them for every screen: specs repo and design export, resources root, legacy label endpoint, legacy key prefix, web i18n convention, Confluence target, CMS taxonomy and document language. Secrets live only in a keychain item or a `--headers-file`, never in the mapping, the taxonomy file, the docs or a command line. Ask the user only for what is still unknown. Table with examples: [Project settings](references/project-settings.md).
44
29
 
45
30
  ## Where this sits
46
31
 
47
- After the screen spec exists: screen spec, then this map, then publish. If the repo holds a snapshot, the registry and the Suggested files are taken from it (see [publish-and-snapshot](reference/publish-and-snapshot.md)).
32
+ After the screen spec exists: screen spec, then this map, then publish. If the repo holds a snapshot, the registry and the Suggested files are taken from it (see [publish-and-snapshot](references/publish-and-snapshot.md)).
48
33
 
49
34
  ## Steps
50
35
 
51
- The helpers in `scripts/` use nothing beyond the Python 3 standard library, apart from one bash script. Commands and how each source is traced are covered in [sources-and-recipes](reference/sources-and-recipes.md).
36
+ The helpers in `scripts/` use nothing beyond the Python 3 standard library, apart from one bash script. Commands and how each source is traced are covered in [sources-and-recipes](references/sources-and-recipes.md).
52
37
 
53
38
  1. **Enumerate new keys.** Take the union of three sources: the design export's `components-used.md` across every state frame, each component's registry `localizationKeys`, and the keys the screen code wires. Tag each key screen-specific, component-owned or shared. Every key is confirmed by comparing sources, never lifted from one of them.
54
39
  2. **Catch what the cross-reference misses.** First find the type the project generates its keys into (for example `grep -rhoE '\b[A-Z][A-Za-z0-9_]*\.[A-Z][A-Za-z0-9_]*\.[A-Za-z_]+' <dir> | cut -d. -f1 | sort | uniq -c | sort -rn`) and set it as `"keyType"` in the mapping; the built-in default `L10n` is only a fallback, and a project that uses another accessor gets its real keys missed or downgraded to weak. Then run `scan-screen-keys.py --screen-path <dir> --key-type <KeyType>`. A `WARNING: no key chain of ...` line on stderr means the type is wrong: fix it before going on. Each `error` and `dynamic` hit becomes a mapping row; compare the `static` hits with the union from step 1.
@@ -56,13 +41,13 @@ The helpers in `scripts/` use nothing beyond the Python 3 standard library, apar
56
41
  4. **CMS copy.** Run `fetch-annotations.py --mapping <map>.json [--local <export-screen-dir>]`: it reads REST and falls back to `--local` when REST fails. When REST is rate-limited or has no token, dump the annotations through Figma MCP and run it again with `--from-mcp <dump>.json`. Fill `cms` and `cmsNodeId` on each row, and send any TR without EN back to the content team.
57
42
  5. **Trace legacy keys** per platform: spec, then view controller, view model and cell presentation models on iOS; layout `@string/` on Android; call sites on web.
58
43
  6. **Legacy values.** Run `resolve-legacy-values.py --keys "<legacy>" --snapshot-root <res>/Localization/Legacy --langs all`; if the snapshot is out of date, `fetch-legacy-labels.py` refreshes it. A value that cannot be found stays blank and gets a note.
59
- 7. **Assemble the mapping JSON**: one row per element, plus `screenshot`, `figmaFileKey` and `legacyKeyPrefix` where they apply. Schema: [format-and-output](reference/format-and-output.md).
44
+ 7. **Assemble the mapping JSON**: one row per element, plus `screenshot`, `figmaFileKey` and `legacyKeyPrefix` where they apply. Schema: [format-and-output](references/format-and-output.md).
60
45
  8. **Overlay.** Run `render-overlay.py --out <dir> --mapping <map>.json`; it produces `<slug>.overlay.png`, which the mapping's `overlay` field then names.
61
46
  9. **Key screenshots.** Run `render-key-shots.py --out <dir> --mapping <map>.json [--slug]`; it fills `keyshots/` and writes the manifest, which the mapping's `keyshots` field then names.
62
47
  10. **Artifacts.** `build-artifact.py <map>.json --out <dir>` (add `--docx`, `--pdf` or `--all` on request).
63
48
  11. **Spreadsheet.** `build-spreadsheet.py <map>.json --out <dir>` (add `--csv` or `--csv-only`).
64
49
  12. **Gate.** Run `verify-map.py --screen-path <dir> --mapping <map>.json [--resources-root <res>]`, with the accessor from step 2 in the mapping's `keyType` (or passed as `--key-type <KeyType>`). Exit code 2 stops publishing. A PASS that carries the key type `WARNING` is not a pass: set the accessor and run it again.
65
- 13. **Publish.** Ask which target(s): (A) Confluence via `publish-confluence.py`, (B) a specs-repo PR under `localizations/<slug>/`, or both. Details: [publish-and-snapshot](reference/publish-and-snapshot.md).
50
+ 13. **Publish.** Ask which target(s): (A) Confluence via `publish-confluence.py`, (B) a specs-repo PR under `localizations/<slug>/`, or both. Details: [publish-and-snapshot](references/publish-and-snapshot.md).
66
51
 
67
52
  When the Figma frame carries no annotations, say so, explain that keys will be mapped by reading the screen, and confirm the screen state (mock or screenshot) and the anchored element list with the user before rendering.
68
53
 
@@ -96,7 +81,8 @@ Edge cases:
96
81
 
97
82
  ## References
98
83
 
99
- - [sources-and-recipes](reference/sources-and-recipes.md): the origin of every column, legacy tracing on each platform, overlay and keyshot geometry, pitfalls.
100
- - [format-and-output](reference/format-and-output.md): the mapping schema, artifact formats, spreadsheet columns and the script reference.
101
- - [publish-and-snapshot](reference/publish-and-snapshot.md): the snapshot that keeps reads in one repo, Confluence publishing, and the specs-repo PR.
84
+ - [Project settings](references/project-settings.md): the per-project settings table with examples, and the questions to ask at the start.
85
+ - [sources-and-recipes](references/sources-and-recipes.md): the origin of every column, legacy tracing on each platform, overlay and keyshot geometry, pitfalls.
86
+ - [format-and-output](references/format-and-output.md): the mapping schema, artifact formats, spreadsheet columns and the script reference.
87
+ - [publish-and-snapshot](references/publish-and-snapshot.md): the snapshot that keeps reads in one repo, Confluence publishing, and the specs-repo PR.
102
88
  - [example mapping](example-mapping.json): a neutral mapping of nine rows that covers each edge case listed above.
@@ -0,0 +1,22 @@
1
+ # Project settings
2
+
3
+ The per-project settings this skill needs before mapping any screen, with an example for each, and what to ask the user at the start.
4
+
5
+ ## Project settings
6
+
7
+ Nothing company-specific is built in. Establish these once per project (read them from the project's docs or ask), then reuse them for every screen:
8
+
9
+ | Setting | What it is | Example |
10
+ |---|---|---|
11
+ | Specs repo + design export | Screen frames, `components-used.md`, `screenshot.png`, `tree.json` | `design-export/<project>/screens/<slug>/` |
12
+ | Resources root | Suggested new values and the component registry | `resources/Localization/Suggested/<Key>.json` |
13
+ | Legacy label endpoint | For refreshing the legacy snapshot | `--endpoint` + `--headers-file` |
14
+ | Legacy key prefix | Display-only prefix the backend stores | mapping `legacyKeyPrefix`, e.g. `App-` |
15
+ | Web i18n convention | For legacy and/or new web apps | `t('key')` / `$t('key')`, keys in `locales/<lang>.json` |
16
+ | Confluence target | Base URL, space, parent page | `CONFLUENCE_BASE_URL` / `CONFLUENCE_SPACE` / `CONFLUENCE_PARENT` |
17
+ | CMS taxonomy | Property Group / Module buckets in the spreadsheet | `--taxonomy cms-taxonomy.json` |
18
+ | Document language | Headings, legend and labels | `--ui-lang en` (default) or `tr` |
19
+
20
+ Secrets live only in a keychain item or a `--headers-file`. They never go into the mapping, the taxonomy file, the docs or a command line.
21
+
22
+ At the start, ask the user only for what is still unknown: where the implemented screen lives, the Figma file key and node-ids (or a confirmed design-export slug), and, once it is time to publish, the Confluence target and the formats wanted.
@@ -90,30 +90,7 @@ Go through the [Review Checklist](#review-checklist) below.
90
90
 
91
91
  A SwiftUI map file imports both `SwiftUI` and `MapKit`.
92
92
 
93
- ```swift
94
- import SwiftUI
95
- import MapKit
96
-
97
- struct TrailheadMap: View {
98
- @State private var camera: MapCameraPosition = .automatic
99
-
100
- private let lot = CLLocationCoordinate2D(latitude: 46.852, longitude: -121.760)
101
- private let summit = CLLocationCoordinate2D(latitude: 46.853, longitude: -121.727)
102
-
103
- var body: some View {
104
- Map(position: $camera) {
105
- Marker("Parking", coordinate: lot)
106
- Marker("Summit", systemImage: "mountain.2.fill", coordinate: summit)
107
- }
108
- .mapStyle(.standard(elevation: .realistic))
109
- .mapControls {
110
- MapUserLocationButton()
111
- MapCompass()
112
- MapScaleView()
113
- }
114
- }
115
- }
116
- ```
93
+ Code: [SwiftUI Map (iOS 17+)](references/code-examples.md#swiftui-map-ios-17).
117
94
 
118
95
  ### Marker or Annotation
119
96
 
@@ -174,26 +151,7 @@ region are in [Camera Control](references/mapkit-patterns.md#camera-control).
174
151
  The value in `.tag(_:)` must have the same type as the `selection` binding,
175
152
  or taps select nothing.
176
153
 
177
- ```swift
178
- struct CampsiteMap: View {
179
- let sites: [Campsite]
180
- @State private var chosen: MKMapItem?
181
- @State private var headline = ""
182
-
183
- var body: some View {
184
- Map(selection: $chosen) {
185
- ForEach(sites) { site in
186
- Marker(site.name, coordinate: site.coordinate)
187
- .tag(site.mapItem)
188
- }
189
- }
190
- .onChange(of: chosen) { _, item in
191
- guard let item else { return }
192
- headline = item.name ?? ""
193
- }
194
- }
195
- }
196
- ```
154
+ Code: [Selection](references/code-examples.md#selection).
197
155
 
198
156
  ## CoreLocation
199
157
 
@@ -210,39 +168,7 @@ element is a `CLLocationUpdate` whose `location` is optional. Rules:
210
168
  sufficiently in use). React with a visible fallback instead of waiting on a
211
169
  sequence that will not deliver.
212
170
 
213
- ```swift
214
- import CoreLocation
215
- import Observation
216
-
217
- @MainActor
218
- @Observable
219
- final class RunTracker {
220
- private(set) var lastFix: CLLocation?
221
- private var stream: Task<Void, Never>?
222
-
223
- func begin() {
224
- stream?.cancel()
225
- stream = Task {
226
- do {
227
- for try await reading in CLLocationUpdate.liveUpdates() {
228
- guard let fix = reading.location,
229
- fix.horizontalAccuracy >= 0,
230
- fix.horizontalAccuracy < 50 else { continue }
231
- lastFix = fix
232
- }
233
- } catch is CancellationError {
234
- } catch {
235
- lastFix = nil
236
- }
237
- }
238
- }
239
-
240
- func end() {
241
- stream?.cancel()
242
- stream = nil
243
- }
244
- }
245
- ```
171
+ Code: [CLLocationUpdate.liveUpdates() (iOS 17+)](references/code-examples.md#cllocationupdateliveupdates-ios-17).
246
172
 
247
173
  Diagnostics, the full filter set and a mapping from each state to UI are in
248
174
  the [location reference](references/mapkit-corelocation-patterns.md#diagnostics-and-fallbacks-ios-18).
@@ -271,24 +197,7 @@ let nearbySession = CLServiceSession(
271
197
 
272
198
  A denied user should see why the feature is empty and a way out:
273
199
 
274
- ```swift
275
- struct LocationOffView: View {
276
- @Environment(\.openURL) private var openURL
277
-
278
- var body: some View {
279
- ContentUnavailableView {
280
- Label("Location is off", systemImage: "location.slash")
281
- } description: {
282
- Text("Allow location access to see trails near you.")
283
- } actions: {
284
- Button("Open Settings") {
285
- guard let settings = URL(string: UIApplication.openSettingsURLString) else { return }
286
- openURL(settings)
287
- }
288
- }
289
- }
290
- }
291
- ```
200
+ Code: [Authorization](references/code-examples.md#authorization).
292
201
 
293
202
  ## Geocoding
294
203
 
@@ -459,3 +368,4 @@ func mapItem(near point: CLLocationCoordinate2D, named name: String) async throw
459
368
  - [Location patterns](references/mapkit-corelocation-patterns.md):
460
369
  `CLLocationUpdate`, `CLServiceSession`, `CLMonitor`, background location,
461
370
  testing, privacy keys.
371
+ - [Code examples](references/code-examples.md): open for the full code behind SwiftUI Map (iOS 17+), Selection, CLLocationUpdate.liveUpdates() (iOS 17+), Authorization.