@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.
- package/CHANGELOG.md +37 -0
- package/docs/facts.json +2 -2
- package/docs/features.md +1 -1
- package/docs/recovery-guide.md +1 -1
- package/install/catalog-history.json +1 -1
- package/manifest.json +225 -148
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/SKILL.md +33 -154
- package/pipeline/commands/multi-agent/analysis/SKILL.md +14 -68
- package/pipeline/commands/multi-agent/analysis-jira/SKILL.md +5 -8
- package/pipeline/commands/multi-agent/autopilot-on/SKILL.md +38 -109
- package/pipeline/commands/multi-agent/channels/SKILL.md +38 -167
- package/pipeline/commands/multi-agent/complaint-analysis/SKILL.md +37 -144
- package/pipeline/commands/multi-agent/design-check/SKILL.md +27 -131
- package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +22 -48
- package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +37 -180
- package/pipeline/commands/multi-agent/jira/SKILL.md +16 -46
- package/pipeline/commands/multi-agent/manual-test/SKILL.md +12 -7
- package/pipeline/commands/multi-agent/refactor/SKILL.md +23 -184
- package/pipeline/commands/multi-agent/resume/SKILL.md +30 -136
- package/pipeline/commands/multi-agent/review/SKILL.md +24 -199
- package/pipeline/commands/multi-agent/setup/SKILL.md +25 -181
- package/pipeline/commands/multi-agent/stack/SKILL.md +9 -53
- package/pipeline/commands/multi-agent/status/SKILL.md +13 -2
- package/pipeline/commands/multi-agent/store-ready/SKILL.md +27 -226
- package/pipeline/commands/multi-agent/sync/SKILL.md +35 -164
- package/pipeline/commands/multi-agent/test-gen/SKILL.md +2 -4
- package/pipeline/commands/multi-agent/update/SKILL.md +18 -60
- package/pipeline/contract/CHANGELOG.md +9 -0
- package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
- package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
- package/pipeline/contract/fixtures/error-unsigned.json +1 -1
- package/pipeline/contract/fixtures/runs-awaiting-question.json +1 -1
- package/pipeline/contract/fixtures/runs-empty.json +1 -1
- package/pipeline/contract/fixtures/runs-failed.json +1 -1
- package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +1 -1
- package/pipeline/contract/fixtures/runs-pr-opened.json +1 -1
- package/pipeline/contract/fixtures/runs-running.json +1 -1
- package/pipeline/contract/manifest.json +5 -5
- package/pipeline/contract/types/index.d.ts +3 -2
- package/pipeline/multi-agent-refs/audit-guide.md +20 -0
- package/pipeline/multi-agent-refs/autopilot-on/details.md +148 -0
- package/pipeline/multi-agent-refs/channels/generator.md +14 -0
- package/pipeline/multi-agent-refs/channels/preview.md +13 -0
- package/pipeline/multi-agent-refs/channels/reference.md +46 -0
- package/pipeline/multi-agent-refs/complaint-analysis/phases.md +159 -0
- package/pipeline/multi-agent-refs/design-check/export.md +1 -0
- package/pipeline/multi-agent-refs/design-check/init-inventory.md +1 -1
- package/pipeline/multi-agent-refs/design-check/scope-and-ledger.md +64 -0
- package/pipeline/multi-agent-refs/features/analysis-command-notes.md +53 -0
- package/pipeline/multi-agent-refs/features/commit-multi-repo.md +63 -0
- package/pipeline/multi-agent-refs/features/dev-multi-repo.md +51 -0
- package/pipeline/multi-agent-refs/features/test-gap.md +40 -0
- package/pipeline/multi-agent-refs/features/worktree-finalize.md +1 -1
- package/pipeline/multi-agent-refs/garbage-collect/phases.md +60 -0
- package/pipeline/multi-agent-refs/ios-coding-standard/plan.md +71 -0
- package/pipeline/multi-agent-refs/ios-coding-standard/resolve-and-scan.md +135 -0
- package/pipeline/multi-agent-refs/jira/picker-detail.md +71 -0
- package/pipeline/multi-agent-refs/orchestrator/copilot-pipeline.md +88 -0
- package/pipeline/multi-agent-refs/orchestrator/dispatcher.md +96 -1
- package/pipeline/multi-agent-refs/orchestrator/phase-3-user-test.md +12 -9
- package/pipeline/multi-agent-refs/phases/phase-1-plan.md +4 -4
- package/pipeline/multi-agent-refs/phases/phase-2-dev.md +8 -47
- package/pipeline/multi-agent-refs/phases/phase-3-review.md +21 -65
- package/pipeline/multi-agent-refs/phases/phase-4-commit.md +9 -59
- package/pipeline/multi-agent-refs/phases/phase-5-report.md +0 -31
- package/pipeline/multi-agent-refs/refactor/sweep.md +190 -0
- package/pipeline/multi-agent-refs/resume/details.md +115 -2
- package/pipeline/multi-agent-refs/review/inputs.md +33 -1
- package/pipeline/multi-agent-refs/review/module-guides.md +9 -1
- package/pipeline/multi-agent-refs/review/panel.md +75 -0
- package/pipeline/multi-agent-refs/review/valven.md +5 -1
- package/pipeline/multi-agent-refs/rules.md +1 -1
- package/pipeline/multi-agent-refs/setup/hooks-and-plugins.md +4 -0
- package/pipeline/multi-agent-refs/setup/token-save-flow.md +13 -0
- package/pipeline/multi-agent-refs/stack/background.md +72 -0
- package/pipeline/multi-agent-refs/store-ready/gates.md +139 -0
- package/pipeline/multi-agent-refs/store-ready/report.md +40 -2
- package/pipeline/multi-agent-refs/sync/office.md +10 -0
- package/pipeline/multi-agent-refs/sync/overview.md +61 -0
- package/pipeline/multi-agent-refs/sync/repo.md +91 -0
- package/pipeline/multi-agent-refs/update/details.md +83 -0
- package/pipeline/office/index.html +6 -3
- package/pipeline/office/toolkit.json +1 -1
- package/pipeline/rules/figma-pipeline.md +6 -3
- package/pipeline/schemas/prefs.schema.json +1 -1
- package/pipeline/schemas/runs-index.schema.json +2 -1
- package/pipeline/scripts/_memory-policy.mjs +27 -5
- package/pipeline/scripts/checkpoint-fold.sh +59 -0
- package/pipeline/scripts/contract-server.mjs +1 -1
- package/pipeline/scripts/gate-ledger.mjs +1 -1
- package/pipeline/scripts/office-control.mjs +41 -15
- package/pipeline/scripts/offload-ref.sh +1 -1
- package/pipeline/scripts/rework-rulings.mjs +5 -3
- package/pipeline/scripts/runs-index.mjs +34 -7
- package/pipeline/scripts/scope-check-gate.mjs +15 -2
- package/pipeline/scripts/status-overview.mjs +401 -0
- package/pipeline/scripts/user-test-handoff.sh +203 -0
- package/pipeline/scripts/validate-planning.mjs +5 -3
- package/pipeline/scripts/validate-triage.mjs +2 -1
- package/pipeline/scripts/write-state.mjs +13 -4
- package/pipeline/skills/.skill-manifest.json +64 -64
- package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +7 -70
- package/pipeline/skills/shared/core/apple-archive-compliance/references/examples.md +67 -0
- package/pipeline/skills/shared/core/apple-archive-compliance/references/version-history.md +8 -0
- package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +10 -72
- package/pipeline/skills/shared/core/google-play-compliance/references/examples.md +69 -0
- package/pipeline/skills/shared/core/google-play-compliance/references/notes.md +7 -0
- package/pipeline/skills/shared/core/google-play-compliance/references/version-history.md +5 -0
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +39 -262
- package/pipeline/skills/shared/core/multi-agent-autopilot-on/SKILL.md +35 -73
- package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +37 -337
- package/pipeline/skills/shared/core/multi-agent-design-check/SKILL.md +27 -122
- package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +8 -340
- package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +37 -180
- package/pipeline/skills/shared/core/multi-agent-manual-test/SKILL.md +12 -7
- package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +23 -184
- package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +14 -82
- package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +25 -277
- package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +13 -2
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +73 -217
- package/pipeline/skills/shared/external/alarmkit/SKILL.md +11 -162
- package/pipeline/skills/shared/external/alarmkit/references/alarmkit-code.md +257 -0
- package/pipeline/skills/shared/external/android-architecture/SKILL.md +7 -58
- package/pipeline/skills/shared/external/android-architecture/references/code-examples.md +68 -0
- package/pipeline/skills/shared/external/app-intents/SKILL.md +3 -45
- package/pipeline/skills/shared/external/app-intents/references/code-examples.md +54 -0
- package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +5 -81
- package/pipeline/skills/shared/external/app-store-optimization/references/engagement-and-localization.md +96 -0
- package/pipeline/skills/shared/external/app-store-review/SKILL.md +6 -112
- package/pipeline/skills/shared/external/app-store-review/references/monetization-and-submission.md +138 -0
- package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +10 -117
- package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models-code.md +186 -0
- package/pipeline/skills/shared/external/authentication/SKILL.md +3 -50
- package/pipeline/skills/shared/external/authentication/references/code-examples.md +59 -0
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +3 -84
- package/pipeline/skills/shared/external/callkit-voip/references/code-examples.md +93 -0
- package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +7 -263
- package/pipeline/skills/shared/external/ci-cd-pipelines/references/pipeline-examples.md +281 -0
- package/pipeline/skills/shared/external/clean-code/SKILL.md +8 -41
- package/pipeline/skills/shared/external/clean-code/references/code-examples.md +56 -0
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +8 -146
- package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-code.md +214 -0
- package/pipeline/skills/shared/external/compose-components/SKILL.md +6 -80
- package/pipeline/skills/shared/external/compose-components/references/code-examples.md +89 -0
- package/pipeline/skills/shared/external/compose-navigation/SKILL.md +5 -64
- package/pipeline/skills/shared/external/compose-navigation/references/code-examples.md +70 -0
- package/pipeline/skills/shared/external/core-data/SKILL.md +9 -157
- package/pipeline/skills/shared/external/core-data/references/core-data-code.md +221 -0
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +3 -89
- package/pipeline/skills/shared/external/core-nfc/references/code-examples.md +98 -0
- package/pipeline/skills/shared/external/coreml/SKILL.md +17 -133
- package/pipeline/skills/shared/external/coreml/references/coreml-code.md +208 -0
- package/pipeline/skills/shared/external/css-modern/SKILL.md +8 -131
- package/pipeline/skills/shared/external/css-modern/references/code-examples.md +142 -0
- package/pipeline/skills/shared/external/database-patterns/SKILL.md +6 -103
- package/pipeline/skills/shared/external/database-patterns/references/orm-examples.md +112 -0
- package/pipeline/skills/shared/external/html-semantic/SKILL.md +7 -48
- package/pipeline/skills/shared/external/html-semantic/references/code-examples.md +54 -0
- package/pipeline/skills/shared/external/humanizer/SKILL.md +6 -19
- package/pipeline/skills/shared/external/humanizer/references/voice.md +24 -0
- package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +13 -170
- package/pipeline/skills/shared/external/ios-accessibility/references/assistive-tech-review.md +224 -0
- package/pipeline/skills/shared/external/ios-localization/SKILL.md +2 -24
- package/pipeline/skills/shared/external/ios-localization/references/code-examples.md +30 -0
- package/pipeline/skills/shared/external/ios-networking/SKILL.md +3 -58
- package/pipeline/skills/shared/external/ios-networking/references/code-examples.md +67 -0
- package/pipeline/skills/shared/external/ios-security/SKILL.md +4 -64
- package/pipeline/skills/shared/external/ios-security/references/code-examples.md +74 -0
- package/pipeline/skills/shared/external/live-activities/SKILL.md +4 -90
- package/pipeline/skills/shared/external/live-activities/references/code-examples.md +108 -0
- package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +9 -23
- package/pipeline/skills/shared/external/localization-reuse-map/references/project-settings.md +22 -0
- package/pipeline/skills/shared/external/mapkit-location/SKILL.md +5 -95
- package/pipeline/skills/shared/external/mapkit-location/references/code-examples.md +117 -0
- package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +7 -106
- package/pipeline/skills/shared/external/nextjs-app-router/references/app-router-examples.md +124 -0
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +2 -31
- package/pipeline/skills/shared/external/permissionkit/references/code-examples.md +37 -0
- package/pipeline/skills/shared/external/play-store-review/SKILL.md +6 -74
- package/pipeline/skills/shared/external/play-store-review/references/signing-and-policies.md +83 -0
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +8 -125
- package/pipeline/skills/shared/external/push-notifications/references/notification-code.md +176 -0
- package/pipeline/skills/shared/external/python-patterns/SKILL.md +7 -158
- package/pipeline/skills/shared/external/python-patterns/references/python-examples.md +176 -0
- package/pipeline/skills/shared/external/rest-api-design/SKILL.md +7 -157
- package/pipeline/skills/shared/external/rest-api-design/references/api-examples.md +175 -0
- package/pipeline/skills/shared/external/swift-architecture/SKILL.md +11 -230
- package/pipeline/skills/shared/external/swift-architecture/references/pattern-examples.md +295 -0
- package/pipeline/skills/shared/external/swift-codable/SKILL.md +17 -149
- package/pipeline/skills/shared/external/swift-codable/references/codable-recipes.md +207 -0
- package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +12 -99
- package/pipeline/skills/shared/external/swift-concurrency/references/code-examples.md +100 -0
- package/pipeline/skills/shared/external/swift-concurrency/references/swift-6-2-additions.md +18 -0
- package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +9 -78
- package/pipeline/skills/shared/external/swift-formatstyle/references/formatstyle-examples.md +113 -0
- package/pipeline/skills/shared/external/swift-language/SKILL.md +3 -56
- package/pipeline/skills/shared/external/swift-language/references/code-examples.md +65 -0
- package/pipeline/skills/shared/external/swift-testing-pro/SKILL.md +1 -0
- package/pipeline/skills/shared/external/swiftdata/SKILL.md +6 -122
- package/pipeline/skills/shared/external/swiftdata/references/code-examples.md +140 -0
- package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +12 -139
- package/pipeline/skills/shared/external/swiftui-uikit-interop/references/interop-code.md +198 -0
- package/pipeline/skills/shared/external/tailwind-css/SKILL.md +9 -81
- package/pipeline/skills/shared/external/tailwind-css/references/code-examples.md +91 -0
- package/pipeline/skills/shared/external/testing-backend/SKILL.md +7 -207
- package/pipeline/skills/shared/external/testing-backend/references/testing-examples.md +225 -0
- package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +8 -89
- package/pipeline/skills/shared/external/typescript-patterns/references/code-examples.md +100 -0
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +2 -20
- package/pipeline/skills/shared/external/vision-framework/references/code-examples.md +26 -0
- package/pipeline/skills/shared/external/vue-composition/SKILL.md +7 -169
- package/pipeline/skills/shared/external/vue-composition/references/vue-examples.md +187 -0
- package/pipeline/skills/shared/external/web-accessibility/SKILL.md +7 -174
- package/pipeline/skills/shared/external/web-accessibility/references/a11y-examples.md +192 -0
- package/pipeline/skills/shared/external/web-performance/SKILL.md +8 -66
- package/pipeline/skills/shared/external/web-performance/references/code-examples.md +77 -0
- package/pipeline/skills/shared/external/web-testing/SKILL.md +11 -186
- package/pipeline/skills/shared/external/web-testing/references/testing-examples.md +213 -0
- /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/format-and-output.md +0 -0
- /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/publish-and-snapshot.md +0 -0
- /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/sources-and-recipes.md +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Other Swift 6.2 Additions
|
|
2
|
+
|
|
3
|
+
Smaller Swift 6.2 additions next to the approachable-concurrency defaults. Each is covered in more depth in
|
|
4
|
+
concurrency-patterns.md.
|
|
5
|
+
|
|
6
|
+
- `Task.immediate` (SE-0472) runs synchronously on the current actor until its
|
|
7
|
+
first suspension instead of being enqueued; `Task.immediateDetached` adds
|
|
8
|
+
detached semantics. Use it where the enqueue delay is visible to the user,
|
|
9
|
+
for example `Task.immediate { await search.apply(query) }`.
|
|
10
|
+
- `Observations { }` (SE-0475) turns reads of `@Observable` properties into an
|
|
11
|
+
`AsyncSequence` with transactional updates:
|
|
12
|
+
`for await elapsed in Observations({ player.elapsed }) { scrubber.move(to: elapsed) }`.
|
|
13
|
+
- Isolated conformances let a main-actor type conform to a protocol whose
|
|
14
|
+
requirements it can only meet on the main actor. The compiler allows the
|
|
15
|
+
conformance only where that isolation holds.
|
|
16
|
+
- SE-0473 adds `.epoch` to `ContinuousClock` and `SuspendingClock`, but the
|
|
17
|
+
iOS 26.4 SDK does not ship it yet ("value of type 'ContinuousClock' has no
|
|
18
|
+
member 'epoch'"); check your SDK before relying on it.
|
|
@@ -107,46 +107,14 @@ passRate.formatted(.percent) // "42%"
|
|
|
107
107
|
|
|
108
108
|
Build exactly the fields you want; the locale decides order and punctuation.
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
launch.formatted(.dateTime.day().month().year()) // "Mar 3, 2026" (order comes from the locale)
|
|
113
|
-
launch.formatted(.dateTime.minute().hour()) // "4:05 PM"
|
|
114
|
-
let spelledOut = Date.FormatStyle.dateTime.day().month(.wide).weekday(.wide)
|
|
115
|
-
launch.formatted(spelledOut) // "Tuesday, March 3"
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Predefined combinations:
|
|
119
|
-
|
|
120
|
-
```swift
|
|
121
|
-
launch.formatted(date: .long, time: .shortened) // "March 3, 2026 at 4:05 PM"
|
|
122
|
-
launch.formatted(date: .abbreviated, time: .omitted) // "Mar 3, 2026"
|
|
123
|
-
launch.formatted(.iso8601) // "2026-03-03T13:05:00Z"
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
Relative to now:
|
|
127
|
-
|
|
128
|
-
```swift
|
|
129
|
-
let dayBefore = Date.now.addingTimeInterval(-86_400)
|
|
130
|
-
dayBefore.formatted(.relative(presentation: .named)) // "yesterday"
|
|
131
|
-
let numericAgo = Date.RelativeFormatStyle(presentation: .numeric)
|
|
132
|
-
dayBefore.formatted(numericAgo) // "1 day ago"
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
Ranges and countdowns:
|
|
136
|
-
|
|
137
|
-
```swift
|
|
138
|
-
let slot = launch..<launch.addingTimeInterval(5_400)
|
|
139
|
-
let meetingSpan = Date.IntervalFormatStyle().day().month().minute().hour()
|
|
140
|
-
slot.formatted(meetingSpan) // "Mar 3, 4:05 - 5:35 PM" (ICU prints an en dash)
|
|
141
|
-
|
|
142
|
-
let untilDeparture = Date.now..<Date.now.addingTimeInterval(190_800)
|
|
143
|
-
let countdown = Date.ComponentsFormatStyle(style: .wide, fields: [.hour, .day])
|
|
144
|
-
untilDeparture.formatted(countdown) // "2 days, 5 hours"
|
|
145
|
-
```
|
|
110
|
+
The reference shows predefined combinations, relative-to-now formatting, and
|
|
111
|
+
ranges and countdowns.
|
|
146
112
|
|
|
147
113
|
Related types: `Date.FormatStyle`, `Date.RelativeFormatStyle`,
|
|
148
114
|
`Date.IntervalFormatStyle`, `Date.ComponentsFormatStyle`.
|
|
149
115
|
|
|
116
|
+
Code for this section: [FormatStyle examples](references/formatstyle-examples.md).
|
|
117
|
+
|
|
150
118
|
### Relative to a chosen reference (iOS 18+)
|
|
151
119
|
|
|
152
120
|
`Date.AnchoredRelativeFormatStyle` describes one fixed date, the anchor,
|
|
@@ -166,28 +134,10 @@ style.format(meetup.addingTimeInterval(-3_600)) // "in 1 hour"
|
|
|
166
134
|
A `Duration` (iOS 16+) can be shown as a clock reading with
|
|
167
135
|
`Duration.TimeFormatStyle` or as labeled units with `Duration.UnitsFormatStyle`.
|
|
168
136
|
|
|
169
|
-
```swift
|
|
170
|
-
let ride = Duration.seconds(7_384) // 2 h 3 min 4 s
|
|
171
|
-
ride.formatted(.time(pattern: .hourMinuteSecond)) // "2:03:04"
|
|
172
|
-
ride.formatted(.time(pattern: .hourMinute)) // "2:03"
|
|
173
|
-
ride.formatted(.time(pattern: .minuteSecond)) // "123:04" (minutes are not wrapped into hours)
|
|
174
|
-
|
|
175
|
-
let stopwatch = Duration.TimeFormatStyle.Pattern.minuteSecond(padMinuteToLength: 2, fractionalSecondsLength: 2)
|
|
176
|
-
Duration.milliseconds(3_750).formatted(.time(pattern: stopwatch)) // "00:03.75"
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
```swift
|
|
180
|
-
let clock: Set<Duration.UnitsFormatStyle.Unit> = [.hours, .minutes, .seconds]
|
|
181
|
-
ride.formatted(.units(allowed: clock, width: .abbreviated)) // "2 hr, 3 min, 4 sec"
|
|
182
|
-
ride.formatted(.units(allowed: clock, width: .wide, maximumUnitCount: 1)) // "2 hours"
|
|
183
|
-
|
|
184
|
-
let rest = Duration.seconds(150)
|
|
185
|
-
rest.formatted(.units(allowed: [.minutes, .seconds], width: .wide)) // "2 minutes, 30 seconds"
|
|
186
|
-
rest.formatted(.units(allowed: [.minutes, .seconds], width: .narrow)) // "2m 30s"
|
|
187
|
-
```
|
|
188
|
-
|
|
189
137
|
`maximumUnitCount` caps how many units appear.
|
|
190
138
|
|
|
139
|
+
Code for this section: [FormatStyle examples](references/formatstyle-examples.md).
|
|
140
|
+
|
|
191
141
|
## Measurements
|
|
192
142
|
|
|
193
143
|
```swift
|
|
@@ -297,33 +247,13 @@ Conform to `FormatStyle` and implement `format(_:)`. Add `ParseableFormatStyle`
|
|
|
297
247
|
if the text must also be read back. `FormatStyle` inherits `Codable` and
|
|
298
248
|
`Hashable`, so keep stored properties simple value types.
|
|
299
249
|
|
|
300
|
-
```swift
|
|
301
|
-
struct CompactCountStyle: FormatStyle {
|
|
302
|
-
func format(_ count: Int) -> String {
|
|
303
|
-
switch abs(count) {
|
|
304
|
-
case 1_000_000...: scaled(count, by: 1_000_000) + "M"
|
|
305
|
-
case 1_000...: scaled(count, by: 1_000) + "K"
|
|
306
|
-
default: count.formatted()
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
private func scaled(_ count: Int, by divisor: Double) -> String {
|
|
311
|
-
(Double(count) / divisor).formatted(.number.precision(.fractionLength(0...1)))
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
extension FormatStyle where Self == CompactCountStyle {
|
|
316
|
-
static var compactCount: CompactCountStyle { CompactCountStyle() }
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
let followers = 48_700.formatted(.compactCount) // "48.7K"
|
|
320
|
-
```
|
|
321
|
-
|
|
322
250
|
The static member in the constrained extension is what enables the
|
|
323
251
|
`.compactCount` dot syntax at call sites. (For real apps, `.notation(.compactName)`
|
|
324
252
|
already does this with localized suffixes; a custom style earns its place when
|
|
325
253
|
the rule is your own.)
|
|
326
254
|
|
|
255
|
+
Code for this section: [FormatStyle examples](references/formatstyle-examples.md).
|
|
256
|
+
|
|
327
257
|
## Common mistakes
|
|
328
258
|
|
|
329
259
|
| Mistake | Fix |
|
|
@@ -349,6 +279,7 @@ the rule is your own.)
|
|
|
349
279
|
|
|
350
280
|
## References
|
|
351
281
|
|
|
282
|
+
- [FormatStyle examples](references/formatstyle-examples.md): open when formatting dates or durations beyond the quick reference, or when writing a custom FormatStyle.
|
|
352
283
|
- [FormatStyle](https://developer.apple.com/documentation/foundation/formatstyle)
|
|
353
284
|
- [IntegerFormatStyle](https://developer.apple.com/documentation/foundation/integerformatstyle)
|
|
354
285
|
- [FloatingPointFormatStyle](https://developer.apple.com/documentation/foundation/floatingpointformatstyle)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# FormatStyle examples
|
|
2
|
+
|
|
3
|
+
<!-- toc -->
|
|
4
|
+
- [Dates](#dates)
|
|
5
|
+
- [Durations](#durations)
|
|
6
|
+
- [Custom FormatStyle](#custom-formatstyle)
|
|
7
|
+
<!-- /toc -->
|
|
8
|
+
|
|
9
|
+
Code for date, duration and custom FormatStyle formatting. SKILL.md keeps the quick reference and the locale rules.
|
|
10
|
+
|
|
11
|
+
## Dates
|
|
12
|
+
|
|
13
|
+
Build exactly the fields you want; the locale decides order and punctuation.
|
|
14
|
+
|
|
15
|
+
```swift
|
|
16
|
+
let launch = Date.now
|
|
17
|
+
launch.formatted(.dateTime.day().month().year()) // "Mar 3, 2026" (order comes from the locale)
|
|
18
|
+
launch.formatted(.dateTime.minute().hour()) // "4:05 PM"
|
|
19
|
+
let spelledOut = Date.FormatStyle.dateTime.day().month(.wide).weekday(.wide)
|
|
20
|
+
launch.formatted(spelledOut) // "Tuesday, March 3"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Predefined combinations:
|
|
24
|
+
|
|
25
|
+
```swift
|
|
26
|
+
launch.formatted(date: .long, time: .shortened) // "March 3, 2026 at 4:05 PM"
|
|
27
|
+
launch.formatted(date: .abbreviated, time: .omitted) // "Mar 3, 2026"
|
|
28
|
+
launch.formatted(.iso8601) // "2026-03-03T13:05:00Z"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Relative to now:
|
|
32
|
+
|
|
33
|
+
```swift
|
|
34
|
+
let dayBefore = Date.now.addingTimeInterval(-86_400)
|
|
35
|
+
dayBefore.formatted(.relative(presentation: .named)) // "yesterday"
|
|
36
|
+
let numericAgo = Date.RelativeFormatStyle(presentation: .numeric)
|
|
37
|
+
dayBefore.formatted(numericAgo) // "1 day ago"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Ranges and countdowns:
|
|
41
|
+
|
|
42
|
+
```swift
|
|
43
|
+
let slot = launch..<launch.addingTimeInterval(5_400)
|
|
44
|
+
let meetingSpan = Date.IntervalFormatStyle().day().month().minute().hour()
|
|
45
|
+
slot.formatted(meetingSpan) // "Mar 3, 4:05 - 5:35 PM" (ICU prints an en dash)
|
|
46
|
+
|
|
47
|
+
let untilDeparture = Date.now..<Date.now.addingTimeInterval(190_800)
|
|
48
|
+
let countdown = Date.ComponentsFormatStyle(style: .wide, fields: [.hour, .day])
|
|
49
|
+
untilDeparture.formatted(countdown) // "2 days, 5 hours"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Related types: `Date.FormatStyle`, `Date.RelativeFormatStyle`,
|
|
53
|
+
`Date.IntervalFormatStyle`, `Date.ComponentsFormatStyle`.
|
|
54
|
+
|
|
55
|
+
## Durations
|
|
56
|
+
|
|
57
|
+
A `Duration` (iOS 16+) can be shown as a clock reading with
|
|
58
|
+
`Duration.TimeFormatStyle` or as labeled units with `Duration.UnitsFormatStyle`.
|
|
59
|
+
|
|
60
|
+
```swift
|
|
61
|
+
let ride = Duration.seconds(7_384) // 2 h 3 min 4 s
|
|
62
|
+
ride.formatted(.time(pattern: .hourMinuteSecond)) // "2:03:04"
|
|
63
|
+
ride.formatted(.time(pattern: .hourMinute)) // "2:03"
|
|
64
|
+
ride.formatted(.time(pattern: .minuteSecond)) // "123:04" (minutes are not wrapped into hours)
|
|
65
|
+
|
|
66
|
+
let stopwatch = Duration.TimeFormatStyle.Pattern.minuteSecond(padMinuteToLength: 2, fractionalSecondsLength: 2)
|
|
67
|
+
Duration.milliseconds(3_750).formatted(.time(pattern: stopwatch)) // "00:03.75"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```swift
|
|
71
|
+
let clock: Set<Duration.UnitsFormatStyle.Unit> = [.hours, .minutes, .seconds]
|
|
72
|
+
ride.formatted(.units(allowed: clock, width: .abbreviated)) // "2 hr, 3 min, 4 sec"
|
|
73
|
+
ride.formatted(.units(allowed: clock, width: .wide, maximumUnitCount: 1)) // "2 hours"
|
|
74
|
+
|
|
75
|
+
let rest = Duration.seconds(150)
|
|
76
|
+
rest.formatted(.units(allowed: [.minutes, .seconds], width: .wide)) // "2 minutes, 30 seconds"
|
|
77
|
+
rest.formatted(.units(allowed: [.minutes, .seconds], width: .narrow)) // "2m 30s"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`maximumUnitCount` caps how many units appear.
|
|
81
|
+
|
|
82
|
+
## Custom FormatStyle
|
|
83
|
+
|
|
84
|
+
Conform to `FormatStyle` and implement `format(_:)`. Add `ParseableFormatStyle`
|
|
85
|
+
if the text must also be read back. `FormatStyle` inherits `Codable` and
|
|
86
|
+
`Hashable`, so keep stored properties simple value types.
|
|
87
|
+
|
|
88
|
+
```swift
|
|
89
|
+
struct CompactCountStyle: FormatStyle {
|
|
90
|
+
func format(_ count: Int) -> String {
|
|
91
|
+
switch abs(count) {
|
|
92
|
+
case 1_000_000...: scaled(count, by: 1_000_000) + "M"
|
|
93
|
+
case 1_000...: scaled(count, by: 1_000) + "K"
|
|
94
|
+
default: count.formatted()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private func scaled(_ count: Int, by divisor: Double) -> String {
|
|
99
|
+
(Double(count) / divisor).formatted(.number.precision(.fractionLength(0...1)))
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
extension FormatStyle where Self == CompactCountStyle {
|
|
104
|
+
static var compactCount: CompactCountStyle { CompactCountStyle() }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let followers = 48_700.formatted(.compactCount) // "48.7K"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The static member in the constrained extension is what enables the
|
|
111
|
+
`.compactCount` dot syntax at call sites. (For real apps, `.notation(.compactName)`
|
|
112
|
+
already does this with localized suffixes; a custom style earns its place when
|
|
113
|
+
the rule is your own.)
|
|
@@ -299,41 +299,7 @@ can be dropped into a builder where that helps. More in
|
|
|
299
299
|
|
|
300
300
|
## Codable basics
|
|
301
301
|
|
|
302
|
-
|
|
303
|
-
struct Venue: Codable {
|
|
304
|
-
var venueName: String
|
|
305
|
-
var seatCount: Int
|
|
306
|
-
var isOutdoor: Bool
|
|
307
|
-
var city: String
|
|
308
|
-
|
|
309
|
-
enum CodingKeys: String, CodingKey {
|
|
310
|
-
case venueName = "venue_name", seatCount = "seat_count", isOutdoor = "is_outdoor", location
|
|
311
|
-
}
|
|
312
|
-
enum LocationKeys: String, CodingKey { case city }
|
|
313
|
-
|
|
314
|
-
init(from decoder: Decoder) throws {
|
|
315
|
-
let c = try decoder.container(keyedBy: CodingKeys.self)
|
|
316
|
-
venueName = try c.decode(String.self, forKey: .venueName)
|
|
317
|
-
seatCount = try c.decodeIfPresent(Int.self, forKey: .seatCount) ?? 0
|
|
318
|
-
if let flag = try? c.decode(Bool.self, forKey: .isOutdoor) {
|
|
319
|
-
isOutdoor = flag
|
|
320
|
-
} else {
|
|
321
|
-
isOutdoor = (try c.decodeIfPresent(String.self, forKey: .isOutdoor)) == "true"
|
|
322
|
-
}
|
|
323
|
-
let place = try c.nestedContainer(keyedBy: LocationKeys.self, forKey: .location)
|
|
324
|
-
city = try place.decode(String.self, forKey: .city)
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
func encode(to encoder: Encoder) throws {
|
|
328
|
-
var c = encoder.container(keyedBy: CodingKeys.self)
|
|
329
|
-
try c.encode(venueName, forKey: .venueName)
|
|
330
|
-
try c.encode(seatCount, forKey: .seatCount)
|
|
331
|
-
try c.encode(isOutdoor, forKey: .isOutdoor)
|
|
332
|
-
var place = c.nestedContainer(keyedBy: LocationKeys.self, forKey: .location)
|
|
333
|
-
try place.encode(city, forKey: .city)
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
```
|
|
302
|
+
Code: [Codable basics](references/code-examples.md#codable-basics).
|
|
337
303
|
|
|
338
304
|
- A `CodingKeys: String, CodingKey` enum renames snake_case keys without a
|
|
339
305
|
custom decoder.
|
|
@@ -379,27 +345,7 @@ let tagCounts = allTags.reduce(into: [:]) { tally, tag in tally[tag, default: 0]
|
|
|
379
345
|
Prefer `.formatted()` to `DateFormatter` and `NumberFormatter`: it is typed,
|
|
380
346
|
localized and short.
|
|
381
347
|
|
|
382
|
-
|
|
383
|
-
import Foundation
|
|
384
|
-
|
|
385
|
-
let now = Date()
|
|
386
|
-
now.formatted()
|
|
387
|
-
now.formatted(date: .abbreviated, time: .shortened)
|
|
388
|
-
now.formatted(.dateTime.year().month().day())
|
|
389
|
-
now.addingTimeInterval(-3600).formatted(.relative(presentation: .named))
|
|
390
|
-
|
|
391
|
-
19.99.formatted(.currency(code: "EUR"))
|
|
392
|
-
0.425.formatted(.percent)
|
|
393
|
-
42.5.formatted(.percent)
|
|
394
|
-
2_400_000.formatted(.number.notation(.compactName))
|
|
395
|
-
Measurement(value: 12, unit: UnitLength.kilometers).formatted(.measurement(width: .abbreviated))
|
|
396
|
-
Duration.seconds(4000).formatted(.time(pattern: .hourMinuteSecond))
|
|
397
|
-
Int64(5_300_000).formatted(.byteCount(style: .file))
|
|
398
|
-
["oat", "rye", "spelt"].formatted(.list(type: .and))
|
|
399
|
-
|
|
400
|
-
let cost = try Decimal("EUR 42.50", format: .currency(code: "EUR"))
|
|
401
|
-
let day = try Date("Mar 4, 2026", strategy: .dateTime.month().day().year())
|
|
402
|
-
```
|
|
348
|
+
Code: [Formatting basics](references/code-examples.md#formatting-basics).
|
|
403
349
|
|
|
404
350
|
`.percent` multiplies by 100, so `42.5` prints as `4,250%`; pass `0.425` for
|
|
405
351
|
42.5%. `.compactName` turns 2,400,000 into `2.4M`. `Duration.formatted` needs
|
|
@@ -457,3 +403,4 @@ delimiter, so align the closing quotes with the text you want flush left.
|
|
|
457
403
|
- [references/swift-attributes-interop.md](references/swift-attributes-interop.md):
|
|
458
404
|
`@c`, module selectors, `@specialized`, `@inline(always)`, `@export`,
|
|
459
405
|
`@section` and `@used`.
|
|
406
|
+
- [Code examples](references/code-examples.md): open for the full code behind Codable basics, Formatting basics.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Swift Language 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
|
+
## Codable basics
|
|
6
|
+
|
|
7
|
+
```swift
|
|
8
|
+
struct Venue: Codable {
|
|
9
|
+
var venueName: String
|
|
10
|
+
var seatCount: Int
|
|
11
|
+
var isOutdoor: Bool
|
|
12
|
+
var city: String
|
|
13
|
+
|
|
14
|
+
enum CodingKeys: String, CodingKey {
|
|
15
|
+
case venueName = "venue_name", seatCount = "seat_count", isOutdoor = "is_outdoor", location
|
|
16
|
+
}
|
|
17
|
+
enum LocationKeys: String, CodingKey { case city }
|
|
18
|
+
|
|
19
|
+
init(from decoder: Decoder) throws {
|
|
20
|
+
let c = try decoder.container(keyedBy: CodingKeys.self)
|
|
21
|
+
venueName = try c.decode(String.self, forKey: .venueName)
|
|
22
|
+
seatCount = try c.decodeIfPresent(Int.self, forKey: .seatCount) ?? 0
|
|
23
|
+
if let flag = try? c.decode(Bool.self, forKey: .isOutdoor) {
|
|
24
|
+
isOutdoor = flag
|
|
25
|
+
} else {
|
|
26
|
+
isOutdoor = (try c.decodeIfPresent(String.self, forKey: .isOutdoor)) == "true"
|
|
27
|
+
}
|
|
28
|
+
let place = try c.nestedContainer(keyedBy: LocationKeys.self, forKey: .location)
|
|
29
|
+
city = try place.decode(String.self, forKey: .city)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
func encode(to encoder: Encoder) throws {
|
|
33
|
+
var c = encoder.container(keyedBy: CodingKeys.self)
|
|
34
|
+
try c.encode(venueName, forKey: .venueName)
|
|
35
|
+
try c.encode(seatCount, forKey: .seatCount)
|
|
36
|
+
try c.encode(isOutdoor, forKey: .isOutdoor)
|
|
37
|
+
var place = c.nestedContainer(keyedBy: LocationKeys.self, forKey: .location)
|
|
38
|
+
try place.encode(city, forKey: .city)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Formatting basics
|
|
44
|
+
|
|
45
|
+
```swift
|
|
46
|
+
import Foundation
|
|
47
|
+
|
|
48
|
+
let now = Date()
|
|
49
|
+
now.formatted()
|
|
50
|
+
now.formatted(date: .abbreviated, time: .shortened)
|
|
51
|
+
now.formatted(.dateTime.year().month().day())
|
|
52
|
+
now.addingTimeInterval(-3600).formatted(.relative(presentation: .named))
|
|
53
|
+
|
|
54
|
+
19.99.formatted(.currency(code: "EUR"))
|
|
55
|
+
0.425.formatted(.percent)
|
|
56
|
+
42.5.formatted(.percent)
|
|
57
|
+
2_400_000.formatted(.number.notation(.compactName))
|
|
58
|
+
Measurement(value: 12, unit: UnitLength.kilometers).formatted(.measurement(width: .abbreviated))
|
|
59
|
+
Duration.seconds(4000).formatted(.time(pattern: .hourMinuteSecond))
|
|
60
|
+
Int64(5_300_000).formatted(.byteCount(style: .file))
|
|
61
|
+
["oat", "rye", "spelt"].formatted(.list(type: .and))
|
|
62
|
+
|
|
63
|
+
let cost = try Decimal("EUR 42.50", format: .currency(code: "EUR"))
|
|
64
|
+
let day = try Date("Mar 4, 2026", strategy: .dateTime.month().day().year())
|
|
65
|
+
```
|
|
@@ -6,6 +6,7 @@ metadata:
|
|
|
6
6
|
source: "twostraws/Swift-Testing-Agent-Skill (MIT)"
|
|
7
7
|
author: Paul Hudson
|
|
8
8
|
version: "1.0"
|
|
9
|
+
not-for: swiftdata-pro
|
|
9
10
|
---
|
|
10
11
|
|
|
11
12
|
Write and review Swift Testing code for correctness, modern API usage, and adherence to project conventions. Report only genuine problems - do not nitpick or invent issues.
|
|
@@ -41,47 +41,7 @@ reference semantics and a designated initializer. Model instances are not
|
|
|
41
41
|
`Sendable`: to hand a model to another actor, pass its `PersistentIdentifier`
|
|
42
42
|
(which is `Sendable`) and fetch it again from that actor's context.
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
import SwiftData
|
|
46
|
-
import Foundation
|
|
47
|
-
|
|
48
|
-
@Model
|
|
49
|
-
class Plant {
|
|
50
|
-
var commonName: String
|
|
51
|
-
var acquiredOn: Date
|
|
52
|
-
var needsRepotting: Bool = false
|
|
53
|
-
@Attribute(.externalStorage) var photo: Data?
|
|
54
|
-
@Relationship(deleteRule: .cascade, inverse: \CareEvent.plant)
|
|
55
|
-
var careEvents: [CareEvent] = []
|
|
56
|
-
var bed: GardenBed?
|
|
57
|
-
@Transient var isHighlighted: Bool = false
|
|
58
|
-
|
|
59
|
-
init(commonName: String, acquiredOn: Date = .now) {
|
|
60
|
-
self.commonName = commonName
|
|
61
|
-
self.acquiredOn = acquiredOn
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
@Model
|
|
66
|
-
final class GardenBed {
|
|
67
|
-
var label: String
|
|
68
|
-
@Relationship(deleteRule: .nullify, inverse: \Plant.bed)
|
|
69
|
-
var plants: [Plant] = []
|
|
70
|
-
init(label: String) { self.label = label }
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
@Model
|
|
74
|
-
final class CareEvent {
|
|
75
|
-
var kind: String
|
|
76
|
-
var loggedAt: Date
|
|
77
|
-
var plant: Plant?
|
|
78
|
-
|
|
79
|
-
init(kind: String, loggedAt: Date) {
|
|
80
|
-
self.kind = kind
|
|
81
|
-
self.loggedAt = loggedAt
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
```
|
|
44
|
+
Code: [Defining models](references/code-examples.md#defining-models).
|
|
85
45
|
|
|
86
46
|
Rules that follow from the macro:
|
|
87
47
|
|
|
@@ -129,31 +89,9 @@ Indexes (`#Index`, iOS 18+) are covered in
|
|
|
129
89
|
|
|
130
90
|
## Container setup
|
|
131
91
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
// Named store, shared with extensions, synced to a private CloudKit database.
|
|
137
|
-
let config = ModelConfiguration(
|
|
138
|
-
"Garden",
|
|
139
|
-
isStoredInMemoryOnly: false,
|
|
140
|
-
groupContainer: .identifier("group.example.garden"),
|
|
141
|
-
cloudKitDatabase: .private("iCloud.example.garden")
|
|
142
|
-
)
|
|
143
|
-
let configured = try ModelContainer(for: Plant.self, configurations: config)
|
|
144
|
-
|
|
145
|
-
// Versioned schema with a migration plan.
|
|
146
|
-
let migrating = try ModelContainer(
|
|
147
|
-
for: GardenSchemaV3.Plant.self,
|
|
148
|
-
migrationPlan: GardenMigrationPlan.self
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
// Tests and previews: nothing touches disk.
|
|
152
|
-
let scratch = try ModelContainer(
|
|
153
|
-
for: Plant.self,
|
|
154
|
-
configurations: ModelConfiguration(isStoredInMemoryOnly: true)
|
|
155
|
-
)
|
|
156
|
-
```
|
|
92
|
+
List every stored model type once. Use a `ModelConfiguration` for a named, app-group or CloudKit-synced store, pass the versioned schema and migration plan when one exists, and use `isStoredInMemoryOnly: true` for tests and previews.
|
|
93
|
+
|
|
94
|
+
Code: [Container setup](references/code-examples.md#container-setup).
|
|
157
95
|
|
|
158
96
|
## CloudKit sync
|
|
159
97
|
|
|
@@ -312,62 +250,7 @@ try context.enumerate(sweep, batchSize: 1_000) { plant in
|
|
|
312
250
|
Each shipped schema is an enum that conforms to `VersionedSchema` and nests its
|
|
313
251
|
own model classes. The plan lists them oldest first and the stages between them.
|
|
314
252
|
|
|
315
|
-
|
|
316
|
-
enum GardenSchemaV1: VersionedSchema {
|
|
317
|
-
static let versionIdentifier = Schema.Version(1, 0, 0)
|
|
318
|
-
static var models: [any PersistentModel.Type] { [Plant.self] }
|
|
319
|
-
|
|
320
|
-
@Model final class Plant {
|
|
321
|
-
var name: String
|
|
322
|
-
init(name: String) { self.name = name }
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
enum GardenSchemaV2: VersionedSchema {
|
|
327
|
-
static let versionIdentifier = Schema.Version(2, 0, 0)
|
|
328
|
-
static var models: [any PersistentModel.Type] { [Plant.self] }
|
|
329
|
-
|
|
330
|
-
@Model final class Plant {
|
|
331
|
-
@Attribute(originalName: "name") var commonName: String
|
|
332
|
-
var sunlight: String = "partial"
|
|
333
|
-
init(commonName: String) { self.commonName = commonName }
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
enum GardenSchemaV3: VersionedSchema {
|
|
338
|
-
static let versionIdentifier = Schema.Version(3, 0, 0)
|
|
339
|
-
static var models: [any PersistentModel.Type] { [Plant.self] }
|
|
340
|
-
|
|
341
|
-
@Model final class Plant {
|
|
342
|
-
var commonName: String
|
|
343
|
-
var sunlight: String = "partial"
|
|
344
|
-
var sunlightHours: Int = 4
|
|
345
|
-
init(commonName: String) { self.commonName = commonName }
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
enum GardenMigrationPlan: SchemaMigrationPlan {
|
|
350
|
-
static var schemas: [any VersionedSchema.Type] {
|
|
351
|
-
[GardenSchemaV1.self, GardenSchemaV2.self, GardenSchemaV3.self]
|
|
352
|
-
}
|
|
353
|
-
static var stages: [MigrationStage] {
|
|
354
|
-
[
|
|
355
|
-
.lightweight(fromVersion: GardenSchemaV1.self, toVersion: GardenSchemaV2.self),
|
|
356
|
-
.custom(
|
|
357
|
-
fromVersion: GardenSchemaV2.self, toVersion: GardenSchemaV3.self,
|
|
358
|
-
willMigrate: nil,
|
|
359
|
-
didMigrate: { context in
|
|
360
|
-
let plants = try context.fetch(FetchDescriptor<GardenSchemaV3.Plant>())
|
|
361
|
-
for plant in plants {
|
|
362
|
-
plant.sunlightHours = plant.sunlight == "full" ? 8 : 4
|
|
363
|
-
}
|
|
364
|
-
try context.save()
|
|
365
|
-
}
|
|
366
|
-
)
|
|
367
|
-
]
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
```
|
|
253
|
+
Code: [Schema versions and migration](references/code-examples.md#schema-versions-and-migration).
|
|
371
254
|
|
|
372
255
|
A lightweight stage covers: new optional or defaulted properties, renames
|
|
373
256
|
through `originalName`, removed properties, and new model types. Anything that
|
|
@@ -493,3 +376,4 @@ the main context. Shared preview containers and `PreviewModifier` traits are in
|
|
|
493
376
|
predicate failures, what is supported, safe capture, debugging.
|
|
494
377
|
- [references/indexing.md](references/indexing.md): `#Index`, compound indexes,
|
|
495
378
|
when an index pays off, migration and verification.
|
|
379
|
+
- [Code examples](references/code-examples.md): open for the full code behind Defining models, Container setup, Schema versions and migration.
|