@mmerterden/multi-agent-pipeline 20.8.0 → 20.8.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.
- package/CHANGELOG.md +12 -0
- package/docs/facts.json +1 -1
- package/manifest.json +63 -62
- package/package.json +2 -2
- package/pipeline/lib/redact.mjs +3 -2
- package/pipeline/scripts/gen-skills-index.mjs +13 -1
- package/pipeline/scripts/pre-commit-check.sh +4 -0
- package/pipeline/skills/.skill-manifest.json +21 -21
- package/pipeline/skills/shared/README.md +64 -64
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +2 -2
- package/pipeline/skills/shared/external/alarmkit/evals/evals.json +2 -2
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +6 -0
- package/pipeline/skills/shared/external/app-store-optimization/references/keyword-research-methodology.md +3 -0
- package/pipeline/skills/shared/external/app-store-optimization/references/product-page-variants.md +3 -0
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +3 -4
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +5 -3
- package/pipeline/skills/shared/external/authentication/SKILL.md +29 -17
- package/pipeline/skills/shared/external/authentication/references/keychain-biometric.md +3 -1
- package/pipeline/skills/shared/external/background-processing/SKILL.md +10 -8
- package/pipeline/skills/shared/external/background-processing/references/background-task-patterns.md +7 -7
- package/pipeline/skills/shared/external/core-bluetooth/SKILL.md +4 -2
- package/pipeline/skills/shared/external/core-data/SKILL.md +12 -2
- package/pipeline/skills/shared/external/coreml/SKILL.md +1 -1
- package/pipeline/skills/shared/external/cryptokit/SKILL.md +1 -1
- package/pipeline/skills/shared/external/device-integrity/SKILL.md +11 -5
- package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +3 -3
- package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +1 -1
- package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +8 -7
- package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +2 -2
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +5 -2
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/_shared.py +100 -0
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +45 -26
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-spreadsheet.py +14 -16
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +12 -5
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +2 -1
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +6 -5
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +44 -18
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +5 -2
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +10 -11
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +4 -33
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +12 -59
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +4 -2
- package/pipeline/skills/shared/external/skill-creator/template.md +7 -1
- package/pipeline/skills/shared/external/storekit/references/core-patterns.md +6 -1
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +3 -2
- package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md +1 -1
- package/pipeline/skills/shared/external/swift-security/SKILL.md +10 -8
- package/pipeline/skills/shared/external/swift-security/references/certificate-trust.md +8 -5
- package/pipeline/skills/shared/external/swift-security/references/keychain-fundamentals.md +13 -9
- package/pipeline/skills/shared/external/swift-security/references/secure-enclave.md +7 -6
- package/pipeline/skills/shared/external/swift-testing/SKILL.md +8 -5
- package/pipeline/skills/shared/external/swift-testing/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/swift-testing/references/testing-advanced.md +3 -2
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +5 -3
- package/pipeline/skills/shared/external/swiftui-navigation/SKILL.md +17 -9
- package/pipeline/skills/shared/external/swiftui-navigation/references/navigationstack.md +4 -2
- package/pipeline/skills/shared/external/swiftui-navigation/references/tabview.md +13 -6
- package/pipeline/skills/shared/external/weatherkit/SKILL.md +8 -5
- package/pipeline/skills/shared/external/widgetkit/SKILL.md +15 -7
- package/pipeline/skills/shared/external/widgetkit/references/widgetkit-advanced.md +8 -6
|
@@ -11,42 +11,10 @@ import os
|
|
|
11
11
|
import re
|
|
12
12
|
import sys
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
LSK = re.compile(rf"LocalizationStringKey((?:\.[A-Za-z_][{WORD}]*)+)")
|
|
19
|
-
LITERAL_LOCALIZED = re.compile(rf"\"([{WORD}.]+)\"\.localized\b")
|
|
20
|
-
WEB_T = re.compile(r"(?<![\w$])t\(\s*(['\"])([A-Za-z0-9_.\-]+)\1")
|
|
21
|
-
WEB_DOLLAR_T = re.compile(r"\$t\(\s*(['\"])([A-Za-z0-9_.\-]+)\1")
|
|
22
|
-
TRAILING_MARKER = re.compile(r"\s*\([^()]*\)\s*$")
|
|
23
|
-
COMPONENT_MARKER = re.compile(r"\([^)]*(owned by|bile\u015fen|component)[^)]*\)", re.IGNORECASE)
|
|
24
|
-
PLACEHOLDER = re.compile(r"<[^<>]+>")
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def strip_accessor(chain):
|
|
28
|
-
segments = chain.strip(".").split(".")
|
|
29
|
-
while segments and segments[-1].startswith("localized"):
|
|
30
|
-
segments.pop()
|
|
31
|
-
return ".".join(segments)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def normalize_key(key):
|
|
35
|
-
return ".".join(seg[:1].upper() + seg[1:] for seg in key.split("."))
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def source_files(root):
|
|
39
|
-
if os.path.isfile(root):
|
|
40
|
-
yield root
|
|
41
|
-
return
|
|
42
|
-
for current, dirs, files in os.walk(root):
|
|
43
|
-
probe = current.replace(os.sep, "/") + "/"
|
|
44
|
-
if any(part in probe for part in SKIP_PARTS):
|
|
45
|
-
dirs[:] = []
|
|
46
|
-
continue
|
|
47
|
-
for name in sorted(files):
|
|
48
|
-
if os.path.splitext(name)[1] in EXTENSIONS:
|
|
49
|
-
yield os.path.join(current, name)
|
|
14
|
+
from _shared import (
|
|
15
|
+
LITERAL_KEY, WEB_DOLLAR_T, WEB_T, clean_key, is_component, is_dynamic, lsk_keys, normalize_key, raw_key,
|
|
16
|
+
source_files,
|
|
17
|
+
)
|
|
50
18
|
|
|
51
19
|
|
|
52
20
|
def scan_code(root):
|
|
@@ -61,28 +29,14 @@ def scan_code(root):
|
|
|
61
29
|
continue
|
|
62
30
|
scanned += 1
|
|
63
31
|
corpus.append(text.lower())
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
for pattern, group in ((LITERAL_LOCALIZED, 1), (WEB_T, 2), (WEB_DOLLAR_T, 2)):
|
|
32
|
+
keys.update(key.lower() for key in lsk_keys(text))
|
|
33
|
+
for pattern, group in ((LITERAL_KEY, 1), (WEB_T, 2), (WEB_DOLLAR_T, 2)):
|
|
67
34
|
keys.update(hit.group(group).lower() for hit in pattern.finditer(text))
|
|
68
35
|
return keys, "\n".join(corpus), scanned
|
|
69
36
|
|
|
70
37
|
|
|
71
|
-
def
|
|
72
|
-
|
|
73
|
-
return bool(COMPONENT_MARKER.search(text))
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def is_dynamic(row, key):
|
|
77
|
-
element = str(row.get("element") or "").lower()
|
|
78
|
-
note = str(row.get("note") or "").lower()
|
|
79
|
-
return (
|
|
80
|
-
key.startswith("(dynamic")
|
|
81
|
-
or bool(PLACEHOLDER.search(key))
|
|
82
|
-
or "dinamik" in element
|
|
83
|
-
or "dynamic" in element
|
|
84
|
-
or "enumerate at runtime" in note
|
|
85
|
-
)
|
|
38
|
+
def filled(value):
|
|
39
|
+
return bool(str(value or "").strip())
|
|
86
40
|
|
|
87
41
|
|
|
88
42
|
def suggested_exists(root, key):
|
|
@@ -114,8 +68,7 @@ def main():
|
|
|
114
68
|
map_keys = set()
|
|
115
69
|
|
|
116
70
|
for row in rows:
|
|
117
|
-
|
|
118
|
-
key = raw_key if raw_key.startswith("(dynamic") else TRAILING_MARKER.sub("", raw_key)
|
|
71
|
+
key = clean_key(raw_key(row))
|
|
119
72
|
component = is_component(row)
|
|
120
73
|
dynamic = is_dynamic(row, key)
|
|
121
74
|
if key:
|
|
@@ -136,11 +89,11 @@ def main():
|
|
|
136
89
|
verdict = row.get("verdict")
|
|
137
90
|
new = row.get("new") or {}
|
|
138
91
|
legacy = row.get("legacy") or {}
|
|
139
|
-
if verdict != "new" and not new.get("tr") and not dynamic:
|
|
92
|
+
if verdict != "new" and not filled(new.get("tr")) and not dynamic:
|
|
140
93
|
gaps.append({"key": key, "issue": "new.tr empty"})
|
|
141
|
-
if verdict in ("reuse", "review") and not (legacy.get("en") or legacy.get("tr")):
|
|
94
|
+
if verdict in ("reuse", "review") and not (filled(legacy.get("en")) or filled(legacy.get("tr"))):
|
|
142
95
|
gaps.append({"key": key, "issue": f"verdict={verdict} but no legacy value"})
|
|
143
|
-
if new.get("tr") and new.get("tr") == new.get("en"):
|
|
96
|
+
if filled(new.get("tr")) and new.get("tr") == new.get("en"):
|
|
144
97
|
untranslated.append(key)
|
|
145
98
|
if args.resources_root and not component and not dynamic and key and not suggested_exists(args.resources_root, key):
|
|
146
99
|
gaps.append({"key": key, "issue": f"no Suggested/{normalize_key(key)}.json"})
|
|
@@ -309,9 +309,11 @@ func lookUp(_ text: String) async throws -> MKMapItem? {
|
|
|
309
309
|
Reverse lookup is `MKReverseGeocodingRequest(location:)`. Formatting and
|
|
310
310
|
batch lookups are in [Geocoding and Places on iOS 26](references/mapkit-patterns.md#geocoding-and-places-on-ios-26).
|
|
311
311
|
|
|
312
|
-
### CLGeocoder (
|
|
312
|
+
### CLGeocoder (available since iOS 5, deprecated in iOS 26)
|
|
313
313
|
|
|
314
|
-
Use it only while the deployment target is below iOS 26.
|
|
314
|
+
Use it only while the deployment target is below iOS 26. The SDK's deprecation
|
|
315
|
+
messages point reverse lookups to `MKReverseGeocodingRequest` and forward
|
|
316
|
+
lookups to `MKGeocodingRequest`. Every `CLPlacemark`
|
|
315
317
|
field is optional. `CLGeocoder` is not `Sendable`, so Swift 6 rejects one
|
|
316
318
|
shared global instance; make a geocoder for each lookup instead.
|
|
317
319
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Skill templates
|
|
2
2
|
|
|
3
|
-
Two skeletons. Copy the one that matches the layer, fill it, then delete every line that fails the cut test. The toolkits use these frontmatter keys: `name`, `description`, `allowed-tools`, `user-invocable`, plus an optional `argument-hint
|
|
3
|
+
Two skeletons. Copy the one that matches the layer, fill it, then delete every line that fails the cut test. The toolkits use these frontmatter keys: `name`, `description`, `allowed-tools`, `user-invocable`, plus an optional `argument-hint` and an optional `metadata` block with `version` (semver of the skill text) and `source` (`multi-agent-pipeline` for a first-party skill, otherwise the upstream it derives from, which a `NOTICE-*.md` beside it must list). Drop the block rather than leave a placeholder in it.
|
|
4
4
|
|
|
5
5
|
## Reference skill
|
|
6
6
|
|
|
@@ -10,6 +10,9 @@ name: {aspect-noun}
|
|
|
10
10
|
description: "{What it covers} for {platform}. {When to reach for it}. Triggers: '{phrase}', '{phrase}', '{phrase}'."
|
|
11
11
|
user-invocable: true
|
|
12
12
|
allowed-tools: Bash, Read, Edit, Write, Glob, Grep
|
|
13
|
+
metadata:
|
|
14
|
+
version: {x.y.z}
|
|
15
|
+
source: {multi-agent-pipeline | upstream}
|
|
13
16
|
---
|
|
14
17
|
|
|
15
18
|
# {name} - {short title}
|
|
@@ -44,6 +47,9 @@ description: "{What it does} for {platform}, with {guardrails}. Use when {situat
|
|
|
44
47
|
user-invocable: true
|
|
45
48
|
argument-hint: "{args}"
|
|
46
49
|
allowed-tools: Bash, Read
|
|
50
|
+
metadata:
|
|
51
|
+
version: {x.y.z}
|
|
52
|
+
source: {multi-agent-pipeline | upstream}
|
|
47
53
|
---
|
|
48
54
|
|
|
49
55
|
# {name} - {short title}
|
|
@@ -99,7 +99,7 @@ struct RecipesApp: App {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
private static func watchTransactions() -> Task<Void, Never> {
|
|
102
|
-
Task
|
|
102
|
+
Task(priority: .background) {
|
|
103
103
|
for await incoming in StoreKit.Transaction.updates {
|
|
104
104
|
if case .verified(let receipt) = incoming {
|
|
105
105
|
await EntitlementStore.shared.refresh()
|
|
@@ -114,6 +114,11 @@ struct RecipesApp: App {
|
|
|
114
114
|
Unverified updates fall through untouched. For each verified one,
|
|
115
115
|
entitlements are recomputed first and the transaction finished afterwards.
|
|
116
116
|
|
|
117
|
+
A plain `Task` is enough here. It inherits the main actor from the `App`, but
|
|
118
|
+
the loop spends its time suspended in `for await` and the entitlement refresh
|
|
119
|
+
runs on the main-actor `EntitlementStore` anyway; `Task.detached` would only add
|
|
120
|
+
a hop and drop the task-local values.
|
|
121
|
+
|
|
117
122
|
## Computing Entitlements
|
|
118
123
|
|
|
119
124
|
```swift
|
|
@@ -7,10 +7,11 @@ metadata:
|
|
|
7
7
|
|
|
8
8
|
# Swift Concurrency
|
|
9
9
|
|
|
10
|
-
Baseline: Swift 6.
|
|
10
|
+
Baseline: Swift 6.2 compilers and later. The aim of every change made with this
|
|
11
11
|
skill is data-race safety through isolation and `Sendable`, reached with the
|
|
12
12
|
smallest possible change in behavior. Features newer than Swift 6.0 name the
|
|
13
|
-
release or proposal that introduced them.
|
|
13
|
+
release or proposal that introduced them; the few that need Swift 6.3, such as
|
|
14
|
+
`weak let` (SE-0481), are marked as 6.3 only.
|
|
14
15
|
|
|
15
16
|
Not for the smallest fix to a single diagnostic (use `swift-concurrency-expert`)
|
|
16
17
|
or a file-by-file review pass (use `swift-concurrency-pro`).
|
package/pipeline/skills/shared/external/swift-concurrency/references/concurrency-patterns.md
CHANGED
|
@@ -157,7 +157,7 @@ nonisolated struct OffMainBackupBatch {
|
|
|
157
157
|
|
|
158
158
|
`weak let` declares a weak reference that is never reassigned. Because the
|
|
159
159
|
binding cannot change, a type storing only `weak let` references to `Sendable`
|
|
160
|
-
objects can itself be `Sendable`.
|
|
160
|
+
objects can itself be `Sendable`. Swift 6.3 only.
|
|
161
161
|
|
|
162
162
|
```swift
|
|
163
163
|
final class SessionObserver: Sendable {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: swift-security
|
|
3
|
-
description: "Apple client security: Keychain (SecItem, kSecClass, OSStatus, iOS
|
|
3
|
+
description: "Apple client security: Keychain (SecItem, kSecClass, OSStatus, iOS, macOS), Face ID/Touch ID keychain items, CryptoKit (AES-GCM, ChaChaPoly, ECDSA, ECDH, HPKE, ML-KEM), Secure Enclave, OAuth token and API key storage, SecTrust/SPKI pinning, keychain sharing, moving secrets out of UserDefaults or plists, OWASP MASVS/MASTG. Use when writing, reviewing or migrating code that stores secrets, holds keys or evaluates trust. Not for passkeys, App Attest or App Review."
|
|
4
4
|
metadata:
|
|
5
5
|
source: multi-agent-pipeline
|
|
6
6
|
---
|
|
@@ -81,9 +81,9 @@ and should be worded that way.
|
|
|
81
81
|
`errSecDuplicateItem`, call `SecItemUpdate`. Deleting and re-adding is
|
|
82
82
|
reserved for changing an item's access control, which `SecItemUpdate`
|
|
83
83
|
cannot do.
|
|
84
|
-
6. `SecItem*` calls must not run on the main actor. Isolate them in an actor
|
|
85
|
-
iOS
|
|
86
|
-
|
|
84
|
+
6. `SecItem*` calls must not run on the main actor. Isolate them in an actor
|
|
85
|
+
(available from iOS 13) or, in completion-handler code, a private serial
|
|
86
|
+
queue.
|
|
87
87
|
7. On macOS, pass `kSecUseDataProtectionKeychain: true` unless the code is
|
|
88
88
|
deliberately working with the legacy file-based keychain.
|
|
89
89
|
8. Never encrypt twice with the same AES-GCM nonce under one key.
|
|
@@ -111,9 +111,10 @@ These answers apply across every reference file in this skill.
|
|
|
111
111
|
`.privateKeyUsage` in its flags, for example
|
|
112
112
|
`[.privateKeyUsage, .biometryCurrentSet]`.
|
|
113
113
|
- Any code path that shows Face ID needs `NSFaceIDUsageDescription` in
|
|
114
|
-
`Info.plist`.
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
`Info.plist`. The `LAContext` header asks apps to supply it, shows its string
|
|
115
|
+
the first time Face ID is used, and fails evaluations with
|
|
116
|
+
`LAError.biometryNotAvailable` once the user denies Face ID for the app. That
|
|
117
|
+
applies to keychain reads that trigger Face ID as much as to `evaluatePolicy`.
|
|
117
118
|
- `arc4random` on Apple platforms is backed by a cryptographically secure
|
|
118
119
|
generator, not RC4. Its limits are a 32-bit result and the bias of `% n`. For
|
|
119
120
|
keys, nonces, salts and tokens, use `SecRandomCopyBytes`,
|
|
@@ -130,7 +131,8 @@ in hardware, and checks of server trust. Adjacent topics appear here only to
|
|
|
130
131
|
point at the right skill:
|
|
131
132
|
|
|
132
133
|
- `authentication`: passkeys, Sign in with Apple, OAuth sign-in flows,
|
|
133
|
-
`ASAuthorizationController`, credential state, sign-in UX
|
|
134
|
+
`ASAuthorizationController`, credential state, sign-in UX, and `LAContext`
|
|
135
|
+
prompts and policy that do not guard a keychain item.
|
|
134
136
|
- `cryptokit`: CryptoKit questions with no storage, keychain, Secure Enclave,
|
|
135
137
|
trust or compliance angle.
|
|
136
138
|
- `device-integrity`: DeviceCheck and App Attest attestations and assertions.
|
|
@@ -4,7 +4,8 @@ What is here: the Security types for certificates, trust, identities and policie
|
|
|
4
4
|
pinning, whether by leaf, by issuing CA, by public-key digest or declaratively through
|
|
5
5
|
`NSPinnedDomains`; building your own trust policy; mutual TLS; the points where App
|
|
6
6
|
Transport Security and trust evaluation overlap; and running a pin set in production.
|
|
7
|
-
It applies from iOS 12
|
|
7
|
+
It applies from iOS 12 and macOS 10.14 through the current releases (checked
|
|
8
|
+
against the iOS 26.4 SDK).
|
|
8
9
|
|
|
9
10
|
What is not here: transport concerns other than checking TLS certificates, managing
|
|
10
11
|
certificates on your servers, and ATS as a configuration topic (the `ios-networking`
|
|
@@ -72,7 +73,7 @@ func checkTrustOffMain(_ trust: SecTrust, then report: @escaping @Sendable (Trus
|
|
|
72
73
|
`nonisolated(unsafe)`: the caller promises not to touch the trust while it is
|
|
73
74
|
being evaluated. `report` is `@Sendable` because it runs on that queue.
|
|
74
75
|
|
|
75
|
-
|
|
76
|
+
The iOS 26.4 SDK still has no async/await version of these calls. A continuation
|
|
76
77
|
bridges the gap:
|
|
77
78
|
|
|
78
79
|
```swift
|
|
@@ -477,7 +478,8 @@ Storing certificates and identities in the Keychain is covered in
|
|
|
477
478
|
- ATS requires, for every `URLSession` connection: TLS 1.2 or later, RSA keys of at
|
|
478
479
|
least 2048 bits or ECC of at least 256 bits, SHA-256 or stronger certificate
|
|
479
480
|
signatures, AES-128 or AES-256, and forward secrecy.
|
|
480
|
-
-
|
|
481
|
+
- For connections to IP-address literals, check Apple's `NSAppTransportSecurity`
|
|
482
|
+
documentation for how ATS applies on the deployment target.
|
|
481
483
|
- Keys that draw App Review scrutiny: `NSAllowsArbitraryLoads`,
|
|
482
484
|
`NSAllowsArbitraryLoadsForMedia`, `NSAllowsArbitraryLoadsInWebContent`,
|
|
483
485
|
`NSExceptionAllowsInsecureHTTPLoads`, `NSExceptionMinimumTLSVersion`.
|
|
@@ -491,8 +493,9 @@ Storing certificates and identities in the Keychain is covered in
|
|
|
491
493
|
| iOS 13 / macOS 10.15 | `SecTrustEvaluateAsyncWithError`; `SecTrustEvaluate` deprecated |
|
|
492
494
|
| iOS 14 / macOS 11 | `NSPinnedDomains`; `SecTrustCopyKey` replaces `SecTrustCopyPublicKey` |
|
|
493
495
|
| iOS 15 / macOS 12 | `SecTrustCopyCertificateChain` replaces `SecTrustGetCertificateAtIndex` and `SecTrustGetCertificateCount` |
|
|
494
|
-
| iOS
|
|
495
|
-
| iOS 18 / macOS 15 |
|
|
496
|
+
| iOS 18 / macOS 15 | `SecCertificateCopyNotValidBeforeDate` and `SecCertificateCopyNotValidAfterDate` |
|
|
497
|
+
| iOS 18.4 / macOS 15.4 | `kSecTrustQWACValidation`, `kSecTrustQCStatements` and the `kSecPolicyApple*` server and client policy constants |
|
|
498
|
+
| iOS 26 / macOS 26 | No new trust-evaluation API; still no async/await form of `SecTrustEvaluateAsyncWithError` |
|
|
496
499
|
|
|
497
500
|
## Threads and performance
|
|
498
501
|
|
|
@@ -18,7 +18,7 @@ and Best Practices".
|
|
|
18
18
|
- [Reading](#reading)
|
|
19
19
|
- [One Place That Builds Queries](#one-place-that-builds-queries)
|
|
20
20
|
- [OSStatus Codes](#osstatus-codes)
|
|
21
|
-
- [An Actor Around the Keychain (iOS
|
|
21
|
+
- [An Actor Around the Keychain (iOS 13+ / macOS 10.15+)](#an-actor-around-the-keychain-ios-13--macos-1015)
|
|
22
22
|
- [Performance](#performance)
|
|
23
23
|
- [macOS: Two Keychains (TN3137)](#macos-two-keychains-tn3137)
|
|
24
24
|
- [Accessibility at a Glance](#accessibility-at-a-glance)
|
|
@@ -85,8 +85,11 @@ Internet passwords are identified by account, server, protocol, authentication
|
|
|
85
85
|
type, port, path, security domain, access group and synchronizable. See
|
|
86
86
|
[keychain-item-classes.md](keychain-item-classes.md) for every class.
|
|
87
87
|
|
|
88
|
-
`SecItemUpdate` cannot change `
|
|
89
|
-
|
|
88
|
+
`SecItemUpdate` cannot change `kSecClass`: the class belongs to the query,
|
|
89
|
+
and the attributes dictionary accepts only real keychain attributes. To change
|
|
90
|
+
class, delete the old item and add a new one. A primary-key attribute such as
|
|
91
|
+
`kSecAttrAccount` can be updated; the update fails with `errSecDuplicateItem`
|
|
92
|
+
only when another item already has the resulting primary key.
|
|
90
93
|
|
|
91
94
|
## Saving: Add, Then Update on Duplicate
|
|
92
95
|
|
|
@@ -257,7 +260,7 @@ its merits:
|
|
|
257
260
|
| -128 | `errSecUserCanceled` | The user dismissed the prompt; tell the UI. |
|
|
258
261
|
| -25293 | `errSecAuthFailed` | Authentication failed or the protected item is no longer usable. |
|
|
259
262
|
| -50 | `errSecParam` | A programming error in the dictionary; fix the query. |
|
|
260
|
-
| -
|
|
263
|
+
| -25303 | `errSecNoSuchAttr` | The attribute is not supported (seen on the data protection keychain). |
|
|
261
264
|
|
|
262
265
|
```swift
|
|
263
266
|
struct KeychainError: Error, CustomStringConvertible {
|
|
@@ -272,7 +275,7 @@ struct KeychainError: Error, CustomStringConvertible {
|
|
|
272
275
|
Logs may record the shape of a query (class, service, which flags were set)
|
|
273
276
|
and the status code. They never record `kSecValueData`, tokens or key bytes.
|
|
274
277
|
|
|
275
|
-
## An Actor Around the Keychain (iOS
|
|
278
|
+
## An Actor Around the Keychain (iOS 13+ / macOS 10.15+)
|
|
276
279
|
|
|
277
280
|
Reads of biometric-protected items wait for the user and can take several
|
|
278
281
|
seconds (WWDC 2014 Session 711). Doing that on the main actor freezes the UI.
|
|
@@ -381,7 +384,8 @@ Why an actor rather than a queue:
|
|
|
381
384
|
| `Sendable` | Enforced | Annotate `@Sendable` by hand |
|
|
382
385
|
| Swift 6 | Native | Needs care to pass strict checking |
|
|
383
386
|
|
|
384
|
-
|
|
387
|
+
In completion-handler code that has not moved to Swift concurrency, a private
|
|
388
|
+
serial queue does the same job:
|
|
385
389
|
|
|
386
390
|
```swift
|
|
387
391
|
final class LegacySecretStore {
|
|
@@ -450,7 +454,7 @@ func macSafe(_ query: [CFString: Any]) -> [CFString: Any] {
|
|
|
450
454
|
|
|
451
455
|
When a macOS keychain bug appears, first confirm which backend the call went
|
|
452
456
|
to. An attribute the file-based keychain quietly drops will produce
|
|
453
|
-
`errSecNoSuchAttr` (-
|
|
457
|
+
`errSecNoSuchAttr` (-25303) on the data protection keychain.
|
|
454
458
|
|
|
455
459
|
## Accessibility at a Glance
|
|
456
460
|
|
|
@@ -477,8 +481,8 @@ is a safe default for foreground use. The actor above defaults to
|
|
|
477
481
|
2. Saves fall back to `SecItemUpdate` on -25299.
|
|
478
482
|
3. Every `SecItemCopyMatching` sets at least one `kSecReturn*` key.
|
|
479
483
|
4. The cast of the `CFTypeRef` result matches the return keys and match limit.
|
|
480
|
-
5. No SecItem call runs on `@MainActor` (actor
|
|
481
|
-
|
|
484
|
+
5. No SecItem call runs on `@MainActor` (an actor, available from iOS 13, or
|
|
485
|
+
a private serial queue in completion-handler code).
|
|
482
486
|
6. Add, query and update dictionaries are built fresh for each call.
|
|
483
487
|
7. Keys are `kSec*` constants, not string literals.
|
|
484
488
|
8. Queries are specific: service and account for generic passwords,
|
|
@@ -321,12 +321,13 @@ func pqRoundTrip() throws -> Bool {
|
|
|
321
321
|
```
|
|
322
322
|
|
|
323
323
|
Transport needs no work: in iOS 26, `URLSession` and `Network.framework`
|
|
324
|
-
negotiate quantum-secure TLS 1.3
|
|
325
|
-
|
|
326
|
-
use it. For an app's own end-to-end encryption,
|
|
327
|
-
post-quantum and classical algorithms, such as the
|
|
328
|
-
|
|
329
|
-
|
|
324
|
+
negotiate quantum-secure TLS 1.3 on their own, offering the hybrid
|
|
325
|
+
`X25519MLKEM768` key-exchange group, and CloudKit, push notifications and
|
|
326
|
+
iCloud Private Relay already use it. For an app's own end-to-end encryption,
|
|
327
|
+
prefer a hybrid of post-quantum and classical algorithms, such as the CryptoKit
|
|
328
|
+
X-Wing KEM (`XWingMLKEM768X25519`) in HPKE; an
|
|
329
|
+
`SecureEnclave.MLKEM768.PrivateKey` performs encapsulation and decapsulation
|
|
330
|
+
inside the hardware. The full CryptoKit catalog is in
|
|
330
331
|
[cryptokit-public-key.md](cryptokit-public-key.md).
|
|
331
332
|
|
|
332
333
|
| Release | Enclave-related change |
|
|
@@ -218,7 +218,7 @@ API you correct.
|
|
|
218
218
|
| API | Needs |
|
|
219
219
|
|-----|-------|
|
|
220
220
|
| Exit tests (`processExitsWith:`) | Swift 6.2, Xcode 26.0; not iOS, tvOS, watchOS |
|
|
221
|
-
| Capture lists in exit tests | Swift 6.3 compiler;
|
|
221
|
+
| Capture lists in exit tests | Compiles with the Swift 6.3.1 compiler (Xcode 26.4); earlier compilers not checked. Captured values must be `Sendable` and `Codable` |
|
|
222
222
|
| `Test.cancel(_:)`, `Issue.record(_:severity:)`, image attachments | Swift 6.3, Xcode 26.4 |
|
|
223
223
|
|
|
224
224
|
```swift
|
|
@@ -230,9 +230,12 @@ API you correct.
|
|
|
230
230
|
}
|
|
231
231
|
```
|
|
232
232
|
|
|
233
|
-
Give each captured value an explicit type (`[name = name as Type]`).
|
|
234
|
-
`[status]` fails
|
|
235
|
-
|
|
233
|
+
Give each captured value an explicit type (`[name = name as Type]`). With the
|
|
234
|
+
Swift 6.3.1 compiler a bare `[status]` fails with "Type of captured value
|
|
235
|
+
'status' is ambiguous", a captured value that is not `Codable` fails with "must
|
|
236
|
+
conform to 'Sendable' and 'Codable'", and a closure that reads `status` with no
|
|
237
|
+
capture list fails with "a C function pointer cannot be formed from a closure
|
|
238
|
+
that captures context".
|
|
236
239
|
|
|
237
240
|
A test that calls `Test.cancel(_:)` must be `throws` or `async throws`.
|
|
238
241
|
|
|
@@ -241,7 +244,7 @@ A test that calls `Test.cancel(_:)` must be `throws` or `async throws`.
|
|
|
241
244
|
| Found | Replace with |
|
|
242
245
|
|-------|--------------|
|
|
243
246
|
| `#expect(exitsWith:)` | `await #expect(processExitsWith: .failure) { ... }`. In an iOS app target, test the logic behind a smaller API that does not exit, or move the exit test to a supported host or tool target. |
|
|
244
|
-
| Exit-test closure reading outer values with no capture list | An explicit, typed capture list (`[code = code as Int32]`); Swift 6.3
|
|
247
|
+
| Exit-test closure reading outer values with no capture list | An explicit, typed capture list (`[code = code as Int32]`); verified with Swift 6.3.1; values `Sendable` and `Codable` |
|
|
245
248
|
| `Test.cancel()` in a test that is not throwing | Mark the test `async throws` and call `try Test.cancel("reason")` |
|
|
246
249
|
| A warning that should not fail the run | `Issue.record("message", severity: .warning)`: reported, test still passes; Swift 6.3, Xcode 26.4 |
|
|
247
250
|
| `Attachment(image, named:).record()` | `Attachment.record(image, named: "...", as: .png)`; accepts `UIImage`, `CGImage`, `CIImage`, `NSImage`; Swift 6.3, Xcode 26.4 |
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"assertions": [
|
|
24
24
|
"Replaces exitsWith: with await #expect(processExitsWith: .failure).",
|
|
25
25
|
"States that exit tests need Swift 6.2 and Xcode 26.0 and do not run on iOS, tvOS or watchOS, and suggests testing the non-exiting logic in the app target.",
|
|
26
|
-
"Adds an explicit capture list for badCode,
|
|
26
|
+
"Adds an explicit, typed capture list for badCode, notes it was verified with the Swift 6.3.1 compiler, and requires Sendable plus Codable values.",
|
|
27
27
|
"Makes the cancelling test async throws (or throws) and calls try Test.cancel.",
|
|
28
28
|
"Uses Issue.record with severity: .warning and names the Swift 6.3 and Xcode 26.4 gate.",
|
|
29
29
|
"Uses Attachment.record(chartImage, named:, as: .png) and names the Swift 6.3 and Xcode 26.4 gate."
|
|
@@ -52,7 +52,8 @@ and reports it as cancelled: neither passed nor failed.
|
|
|
52
52
|
## Capturing Values in Exit Tests
|
|
53
53
|
|
|
54
54
|
ST-0012 lets an exit test's closure capture values from the surrounding scope.
|
|
55
|
-
It
|
|
55
|
+
It compiles with the Swift 6.3.1 compiler in Xcode 26.4; earlier compilers were
|
|
56
|
+
not checked. Exit tests themselves arrived in Swift 6.2
|
|
56
57
|
(Xcode 26.0) through the `processExitsWith:` macros. Captured values must be
|
|
57
58
|
`Sendable` and `Codable`, because they are sent to the child process.
|
|
58
59
|
|
|
@@ -101,7 +102,7 @@ import UIKit
|
|
|
101
102
|
|---------|---------|
|
|
102
103
|
| Attachments of standard values | Swift 6.2, Xcode 26.0 |
|
|
103
104
|
| Exit tests with `processExitsWith:` | Swift 6.2, Xcode 26.0; macOS, Linux, FreeBSD, OpenBSD, Windows |
|
|
104
|
-
| Capture lists in exit tests | Swift 6.3 compiler |
|
|
105
|
+
| Capture lists in exit tests | Verified with the Swift 6.3.1 compiler (Xcode 26.4) |
|
|
105
106
|
| `Issue.record(_:severity:)`, `Test.cancel(_:)`, image attachments | Swift 6.3, Xcode 26.4 |
|
|
106
107
|
|
|
107
108
|
iOS, tvOS and watchOS have no exit tests. Put the exiting behaviour behind a
|
|
@@ -35,9 +35,11 @@ shipped without a migration plan.
|
|
|
35
35
|
|
|
36
36
|
## Defining models
|
|
37
37
|
|
|
38
|
-
`@Model` only applies to classes. It adds conformance to `PersistentModel
|
|
39
|
-
`Observable` and `
|
|
40
|
-
designated initializer.
|
|
38
|
+
`@Model` only applies to classes. It adds conformance to `PersistentModel`
|
|
39
|
+
(which refines `Observable`, `Hashable` and `Identifiable`), so a model needs
|
|
40
|
+
reference semantics and a designated initializer. Model instances are not
|
|
41
|
+
`Sendable`: to hand a model to another actor, pass its `PersistentIdentifier`
|
|
42
|
+
(which is `Sendable`) and fetch it again from that actor's context.
|
|
41
43
|
|
|
42
44
|
```swift
|
|
43
45
|
import SwiftData
|
|
@@ -188,19 +188,24 @@ struct RootTabs: View {
|
|
|
188
188
|
The custom binding sends every change through `select`, so a tab like
|
|
189
189
|
"New" can run an action instead of becoming the selected tab.
|
|
190
190
|
|
|
191
|
-
### iOS 26
|
|
191
|
+
### Tab APIs from iOS 18 and iOS 26
|
|
192
192
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
-
|
|
196
|
-
|
|
193
|
+
From iOS 18 (macOS 15):
|
|
194
|
+
|
|
195
|
+
- `Tab(role: .search)` marks the search tab; the iOS 26 tab bar shows it as a
|
|
196
|
+
search field while that tab is active.
|
|
197
197
|
- `.tabViewSidebarHeader { }` and `.tabViewSidebarFooter { }` customise the
|
|
198
198
|
sidebar on iPadOS and macOS.
|
|
199
|
-
- `.tabViewBottomAccessory { }` pins a view just above the tab bar, such as a
|
|
200
|
-
mini player.
|
|
201
199
|
- `TabSection` collects tabs under a sidebar heading; mark it with
|
|
202
200
|
`.tabPlacement(.sidebarOnly)` to keep it out of the compact bar.
|
|
203
201
|
|
|
202
|
+
From iOS 26 only:
|
|
203
|
+
|
|
204
|
+
- `.tabBarMinimizeBehavior(_:)` with `.onScrollDown`, `.onScrollUp` or `.never`.
|
|
205
|
+
iPhone only.
|
|
206
|
+
- `.tabViewBottomAccessory { }` pins a view just above the tab bar, such as a
|
|
207
|
+
mini player.
|
|
208
|
+
|
|
204
209
|
More in [references/tabview.md](references/tabview.md).
|
|
205
210
|
|
|
206
211
|
## Deep links
|
|
@@ -259,7 +264,10 @@ Details, AASA format and scheme parsing: [references/deeplinks.md](references/de
|
|
|
259
264
|
- Driving a model-backed sheet with `.sheet(isPresented:)` instead of
|
|
260
265
|
`.sheet(item:)`.
|
|
261
266
|
- Putting views in the path. The path holds small `Hashable` route values.
|
|
262
|
-
-
|
|
267
|
+
- Writing `NavigationStack(path: $router.path)` where `router` is a plain `let`
|
|
268
|
+
or comes from `@Environment`. That gives no binding and does not compile;
|
|
269
|
+
declare the property `@Bindable var router: Router`, or add
|
|
270
|
+
`@Bindable var router = router` inside `body`.
|
|
263
271
|
- Building tabs with the older `.tabItem { }` instead of `Tab(value:)` with
|
|
264
272
|
`TabView(selection:)`.
|
|
265
273
|
- Expecting `tabBarMinimizeBehavior` to do anything on iPad. It is iPhone only.
|
|
@@ -285,5 +293,5 @@ Details, AASA format and scheme parsing: [references/deeplinks.md](references/de
|
|
|
285
293
|
|
|
286
294
|
- [references/navigationstack.md](references/navigationstack.md): routers, per-tab stacks, centralised destinations, data-driven tabs
|
|
287
295
|
- [references/sheets.md](references/sheets.md): enum sheet routing, sizing, dismissal confirmation
|
|
288
|
-
- [references/tabview.md](references/tabview.md): tab architecture, custom selection binding, dynamic tabs, iOS 26 tab
|
|
296
|
+
- [references/tabview.md](references/tabview.md): tab architecture, custom selection binding, dynamic tabs, the iOS 18 and iOS 26 tab APIs
|
|
289
297
|
- [references/deeplinks.md](references/deeplinks.md): router URL handling, universal links and AASA, custom schemes, Handoff
|
|
@@ -172,5 +172,7 @@ struct DynamicTabs: View {
|
|
|
172
172
|
- A path shared across tabs, unless you really want one global history.
|
|
173
173
|
- Route identifiers that are not stable or not `Hashable`.
|
|
174
174
|
- View instances stored in the path instead of route data.
|
|
175
|
-
- A router
|
|
176
|
-
|
|
175
|
+
- A router passed as a plain `let` or read from `@Environment` and then used as
|
|
176
|
+
`$router.path`: no binding exists without `@Bindable`, so the stack view needs
|
|
177
|
+
`@Bindable var router: Router` (as `ExploreStack` has) or a local
|
|
178
|
+
`@Bindable var router = router` in `body`.
|
|
@@ -107,7 +107,7 @@ enum AppTab: Hashable, Identifiable {
|
|
|
107
107
|
}
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
-
## iOS
|
|
110
|
+
## Tab APIs from iOS 18 and iOS 26
|
|
111
111
|
|
|
112
112
|
```swift
|
|
113
113
|
TabView {
|
|
@@ -121,20 +121,27 @@ TabView {
|
|
|
121
121
|
.tabViewSidebarBottomBar { SettingsButton() }
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
- `Tab(role: .search)`
|
|
127
|
-
active.
|
|
124
|
+
iOS 18 (macOS 15, visionOS 2):
|
|
125
|
+
|
|
126
|
+
- `Tab(role: .search)` marks the search tab; the iOS 26 tab bar replaces the bar
|
|
127
|
+
with a search field while that tab is active.
|
|
128
128
|
- Sidebar customisation: `.tabViewSidebarHeader { }`,
|
|
129
129
|
`.tabViewSidebarFooter { }` and `.tabViewSidebarBottomBar { }`.
|
|
130
|
+
- `TabSection` and `.tabPlacement(_:)`.
|
|
131
|
+
|
|
132
|
+
iOS 26 only:
|
|
133
|
+
|
|
134
|
+
- `TabBarMinimizeBehavior`: `.automatic` (decided by context), `.onScrollDown`,
|
|
135
|
+
`.onScrollUp` and `.never`. The two scroll behaviours apply on iPhone only.
|
|
130
136
|
- `.tabViewBottomAccessory { }` shows content just above the tab bar; its
|
|
131
137
|
placement is described by `TabViewBottomAccessoryPlacement`.
|
|
132
138
|
|
|
139
|
+
The example mixes both groups, so as written it needs iOS 26.
|
|
140
|
+
|
|
133
141
|
## Pitfalls
|
|
134
142
|
|
|
135
143
|
- A view model per tab. Keep state local to views or in shared `@Observable`
|
|
136
144
|
services.
|
|
137
|
-
- `@Observable` objects nested inside each other.
|
|
138
145
|
- An `AppTab.id` that changes between launches; dynamic cases must hash on a
|
|
139
146
|
stable identifier.
|
|
140
147
|
- Special tabs such as compose that change the selection.
|
|
@@ -68,8 +68,9 @@ headline = "\(now.temperature.formatted()), \(now.condition.description)"
|
|
|
68
68
|
|
|
69
69
|
### Hourly
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
current hour
|
|
71
|
+
At the time of writing, Apple's documentation says the default hourly forecast
|
|
72
|
+
covers 25 consecutive hours starting with the current hour; read the count from
|
|
73
|
+
the result rather than hard-coding it. `hourlyForecast` is a `Forecast<HourWeather>`:
|
|
73
74
|
|
|
74
75
|
```swift
|
|
75
76
|
let timeline = report.hourlyForecast.map { slot in
|
|
@@ -79,7 +80,9 @@ let timeline = report.hourlyForecast.map { slot in
|
|
|
79
80
|
|
|
80
81
|
### Daily
|
|
81
82
|
|
|
82
|
-
|
|
83
|
+
At the time of writing, Apple's documentation says the default daily forecast
|
|
84
|
+
covers 10 consecutive days starting today; again, read the count from the
|
|
85
|
+
result.
|
|
83
86
|
`dailyForecast` is a `Forecast<DayWeather>`:
|
|
84
87
|
|
|
85
88
|
```swift
|
|
@@ -145,8 +148,8 @@ if let nextHour = try await forecaster.weather(for: site, including: .minute) {
|
|
|
145
148
|
| Query | Result type | Notes |
|
|
146
149
|
| --- | --- | --- |
|
|
147
150
|
| `.current` | `CurrentWeather` | |
|
|
148
|
-
| `.hourly` | `Forecast<HourWeather>` | 25 hours from now |
|
|
149
|
-
| `.daily` | `Forecast<DayWeather>` | 10 days from today |
|
|
151
|
+
| `.hourly` | `Forecast<HourWeather>` | Default span per Apple's docs: 25 hours from now |
|
|
152
|
+
| `.daily` | `Forecast<DayWeather>` | Default span per Apple's docs: 10 days from today |
|
|
150
153
|
| `.minute` | `Forecast<MinuteWeather>?` | `nil` where unsupported |
|
|
151
154
|
| `.alerts` | `[WeatherAlert]?` | `nil` where unsupported |
|
|
152
155
|
| `.availability` | `WeatherAvailability` | Which region-limited datasets exist here |
|