@kata-sh/cli 0.1.0 → 0.1.2
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/LICENSE +21 -0
- package/README.md +156 -0
- package/dist/app-paths.d.ts +4 -0
- package/dist/app-paths.js +6 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +56 -0
- package/dist/loader.d.ts +2 -0
- package/dist/loader.js +95 -0
- package/dist/resource-loader.d.ts +18 -0
- package/dist/resource-loader.js +50 -0
- package/dist/wizard.d.ts +15 -0
- package/dist/wizard.js +159 -0
- package/package.json +50 -21
- package/pkg/dist/modes/interactive/theme/dark.json +85 -0
- package/pkg/dist/modes/interactive/theme/light.json +84 -0
- package/pkg/dist/modes/interactive/theme/theme-schema.json +335 -0
- package/pkg/dist/modes/interactive/theme/theme.d.ts +78 -0
- package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -0
- package/pkg/dist/modes/interactive/theme/theme.js +949 -0
- package/pkg/dist/modes/interactive/theme/theme.js.map +1 -0
- package/pkg/package.json +8 -0
- package/scripts/postinstall.js +45 -0
- package/src/resources/AGENTS.md +108 -0
- package/src/resources/KATA-WORKFLOW.md +661 -0
- package/src/resources/agents/researcher.md +29 -0
- package/src/resources/agents/scout.md +56 -0
- package/src/resources/agents/worker.md +31 -0
- package/src/resources/extensions/ask-user-questions.ts +200 -0
- package/src/resources/extensions/bg-shell/index.ts +2758 -0
- package/src/resources/extensions/browser-tools/BROWSER-TOOLS-V2-PROPOSAL.md +1277 -0
- package/src/resources/extensions/browser-tools/core.js +1057 -0
- package/src/resources/extensions/browser-tools/index.ts +4916 -0
- package/src/resources/extensions/browser-tools/package.json +20 -0
- package/src/resources/extensions/context7/index.ts +428 -0
- package/src/resources/extensions/context7/package.json +11 -0
- package/src/resources/extensions/get-secrets-from-user.ts +352 -0
- package/src/resources/extensions/github/formatters.ts +207 -0
- package/src/resources/extensions/github/gh-api.ts +537 -0
- package/src/resources/extensions/github/index.ts +778 -0
- package/src/resources/extensions/kata/activity-log.ts +88 -0
- package/src/resources/extensions/kata/auto.ts +2786 -0
- package/src/resources/extensions/kata/commands.ts +355 -0
- package/src/resources/extensions/kata/crash-recovery.ts +85 -0
- package/src/resources/extensions/kata/dashboard-overlay.ts +516 -0
- package/src/resources/extensions/kata/docs/preferences-reference.md +103 -0
- package/src/resources/extensions/kata/doctor.ts +683 -0
- package/src/resources/extensions/kata/files.ts +730 -0
- package/src/resources/extensions/kata/gitignore.ts +165 -0
- package/src/resources/extensions/kata/guided-flow.ts +976 -0
- package/src/resources/extensions/kata/index.ts +556 -0
- package/src/resources/extensions/kata/metrics.ts +397 -0
- package/src/resources/extensions/kata/observability-validator.ts +408 -0
- package/src/resources/extensions/kata/package.json +11 -0
- package/src/resources/extensions/kata/paths.ts +346 -0
- package/src/resources/extensions/kata/preferences.ts +695 -0
- package/src/resources/extensions/kata/prompt-loader.ts +50 -0
- package/src/resources/extensions/kata/prompts/complete-milestone.md +25 -0
- package/src/resources/extensions/kata/prompts/complete-slice.md +27 -0
- package/src/resources/extensions/kata/prompts/discuss.md +151 -0
- package/src/resources/extensions/kata/prompts/doctor-heal.md +29 -0
- package/src/resources/extensions/kata/prompts/execute-task.md +64 -0
- package/src/resources/extensions/kata/prompts/guided-complete-slice.md +1 -0
- package/src/resources/extensions/kata/prompts/guided-discuss-milestone.md +3 -0
- package/src/resources/extensions/kata/prompts/guided-discuss-slice.md +59 -0
- package/src/resources/extensions/kata/prompts/guided-execute-task.md +1 -0
- package/src/resources/extensions/kata/prompts/guided-plan-milestone.md +23 -0
- package/src/resources/extensions/kata/prompts/guided-plan-slice.md +1 -0
- package/src/resources/extensions/kata/prompts/guided-research-slice.md +11 -0
- package/src/resources/extensions/kata/prompts/guided-resume-task.md +1 -0
- package/src/resources/extensions/kata/prompts/plan-milestone.md +47 -0
- package/src/resources/extensions/kata/prompts/plan-slice.md +63 -0
- package/src/resources/extensions/kata/prompts/queue.md +85 -0
- package/src/resources/extensions/kata/prompts/reassess-roadmap.md +48 -0
- package/src/resources/extensions/kata/prompts/replan-slice.md +39 -0
- package/src/resources/extensions/kata/prompts/research-milestone.md +37 -0
- package/src/resources/extensions/kata/prompts/research-slice.md +28 -0
- package/src/resources/extensions/kata/prompts/run-uat.md +109 -0
- package/src/resources/extensions/kata/prompts/system.md +341 -0
- package/src/resources/extensions/kata/session-forensics.ts +550 -0
- package/src/resources/extensions/kata/skill-discovery.ts +137 -0
- package/src/resources/extensions/kata/state.ts +509 -0
- package/src/resources/extensions/kata/templates/context.md +76 -0
- package/src/resources/extensions/kata/templates/decisions.md +8 -0
- package/src/resources/extensions/kata/templates/milestone-summary.md +73 -0
- package/src/resources/extensions/kata/templates/plan.md +133 -0
- package/src/resources/extensions/kata/templates/preferences.md +15 -0
- package/src/resources/extensions/kata/templates/project.md +31 -0
- package/src/resources/extensions/kata/templates/reassessment.md +28 -0
- package/src/resources/extensions/kata/templates/requirements.md +81 -0
- package/src/resources/extensions/kata/templates/research.md +46 -0
- package/src/resources/extensions/kata/templates/roadmap.md +118 -0
- package/src/resources/extensions/kata/templates/slice-context.md +58 -0
- package/src/resources/extensions/kata/templates/slice-summary.md +99 -0
- package/src/resources/extensions/kata/templates/state.md +19 -0
- package/src/resources/extensions/kata/templates/task-plan.md +52 -0
- package/src/resources/extensions/kata/templates/task-summary.md +57 -0
- package/src/resources/extensions/kata/templates/uat.md +54 -0
- package/src/resources/extensions/kata/tests/activity-log-prune.test.ts +327 -0
- package/src/resources/extensions/kata/tests/auto-preflight.test.ts +97 -0
- package/src/resources/extensions/kata/tests/auto-supervisor.test.mjs +53 -0
- package/src/resources/extensions/kata/tests/complete-milestone.test.ts +317 -0
- package/src/resources/extensions/kata/tests/cost-projection.test.ts +160 -0
- package/src/resources/extensions/kata/tests/derive-state-deps.test.ts +477 -0
- package/src/resources/extensions/kata/tests/derive-state.test.ts +1013 -0
- package/src/resources/extensions/kata/tests/doctor.test.ts +718 -0
- package/src/resources/extensions/kata/tests/idle-recovery.test.ts +490 -0
- package/src/resources/extensions/kata/tests/metrics-io.test.ts +254 -0
- package/src/resources/extensions/kata/tests/metrics.test.ts +217 -0
- package/src/resources/extensions/kata/tests/must-have-parser.test.ts +309 -0
- package/src/resources/extensions/kata/tests/parsers.test.ts +1257 -0
- package/src/resources/extensions/kata/tests/plan-milestone.test.ts +185 -0
- package/src/resources/extensions/kata/tests/plan-quality-validator.test.ts +386 -0
- package/src/resources/extensions/kata/tests/reassess-prompt.test.ts +208 -0
- package/src/resources/extensions/kata/tests/replan-slice.test.ts +686 -0
- package/src/resources/extensions/kata/tests/requirements.test.ts +151 -0
- package/src/resources/extensions/kata/tests/resolve-ts-hooks.mjs +17 -0
- package/src/resources/extensions/kata/tests/resolve-ts.mjs +11 -0
- package/src/resources/extensions/kata/tests/run-uat.test.ts +383 -0
- package/src/resources/extensions/kata/tests/unit-runtime.test.ts +388 -0
- package/src/resources/extensions/kata/tests/workspace-index.test.ts +118 -0
- package/src/resources/extensions/kata/tests/worktree.test.ts +222 -0
- package/src/resources/extensions/kata/types.ts +159 -0
- package/src/resources/extensions/kata/unit-runtime.ts +163 -0
- package/src/resources/extensions/kata/workspace-index.ts +203 -0
- package/src/resources/extensions/kata/worktree.ts +182 -0
- package/src/resources/extensions/mac-tools/index.ts +852 -0
- package/src/resources/extensions/mac-tools/swift-cli/Package.swift +22 -0
- package/src/resources/extensions/mac-tools/swift-cli/Sources/main.swift +1318 -0
- package/src/resources/extensions/search-the-web/cache.ts +78 -0
- package/src/resources/extensions/search-the-web/format.ts +258 -0
- package/src/resources/extensions/search-the-web/http.ts +238 -0
- package/src/resources/extensions/search-the-web/index.ts +68 -0
- package/src/resources/extensions/search-the-web/tool-fetch-page.ts +519 -0
- package/src/resources/extensions/search-the-web/tool-llm-context.ts +404 -0
- package/src/resources/extensions/search-the-web/tool-search.ts +503 -0
- package/src/resources/extensions/search-the-web/url-utils.ts +91 -0
- package/src/resources/extensions/shared/confirm-ui.ts +126 -0
- package/src/resources/extensions/shared/interview-ui.ts +822 -0
- package/src/resources/extensions/shared/next-action-ui.ts +235 -0
- package/src/resources/extensions/shared/progress-widget.ts +282 -0
- package/src/resources/extensions/shared/thinking-widget.ts +107 -0
- package/src/resources/extensions/shared/ui.ts +400 -0
- package/src/resources/extensions/shared/wizard-ui.ts +551 -0
- package/src/resources/extensions/slash-commands/audit.ts +92 -0
- package/src/resources/extensions/slash-commands/create-extension.ts +375 -0
- package/src/resources/extensions/slash-commands/create-slash-command.ts +280 -0
- package/src/resources/extensions/slash-commands/index.ts +12 -0
- package/src/resources/extensions/slash-commands/kata-run.ts +34 -0
- package/src/resources/extensions/subagent/agents.ts +126 -0
- package/src/resources/extensions/subagent/index.ts +1293 -0
- package/src/resources/skills/debug-like-expert/SKILL.md +231 -0
- package/src/resources/skills/debug-like-expert/references/debugging-mindset.md +253 -0
- package/src/resources/skills/debug-like-expert/references/hypothesis-testing.md +373 -0
- package/src/resources/skills/debug-like-expert/references/investigation-techniques.md +337 -0
- package/src/resources/skills/debug-like-expert/references/verification-patterns.md +425 -0
- package/src/resources/skills/debug-like-expert/references/when-to-research.md +361 -0
- package/src/resources/skills/frontend-design/SKILL.md +45 -0
- package/src/resources/skills/swiftui/SKILL.md +208 -0
- package/src/resources/skills/swiftui/references/animations.md +921 -0
- package/src/resources/skills/swiftui/references/architecture.md +1561 -0
- package/src/resources/skills/swiftui/references/layout-system.md +1186 -0
- package/src/resources/skills/swiftui/references/navigation.md +1492 -0
- package/src/resources/skills/swiftui/references/networking-async.md +214 -0
- package/src/resources/skills/swiftui/references/performance.md +1706 -0
- package/src/resources/skills/swiftui/references/platform-integration.md +204 -0
- package/src/resources/skills/swiftui/references/state-management.md +1443 -0
- package/src/resources/skills/swiftui/references/swiftdata.md +297 -0
- package/src/resources/skills/swiftui/references/testing-debugging.md +247 -0
- package/src/resources/skills/swiftui/references/uikit-appkit-interop.md +218 -0
- package/src/resources/skills/swiftui/workflows/add-feature.md +191 -0
- package/src/resources/skills/swiftui/workflows/build-new-app.md +311 -0
- package/src/resources/skills/swiftui/workflows/debug-swiftui.md +192 -0
- package/src/resources/skills/swiftui/workflows/optimize-performance.md +197 -0
- package/src/resources/skills/swiftui/workflows/ship-app.md +203 -0
- package/src/resources/skills/swiftui/workflows/write-tests.md +235 -0
- package/dist/commands/task.d.ts +0 -9
- package/dist/commands/task.d.ts.map +0 -1
- package/dist/commands/task.js +0 -129
- package/dist/commands/task.js.map +0 -1
- package/dist/commands/task.test.d.ts +0 -2
- package/dist/commands/task.test.d.ts.map +0 -1
- package/dist/commands/task.test.js +0 -169
- package/dist/commands/task.test.js.map +0 -1
- package/dist/e2e/task-e2e.test.d.ts +0 -2
- package/dist/e2e/task-e2e.test.d.ts.map +0 -1
- package/dist/e2e/task-e2e.test.js +0 -173
- package/dist/e2e/task-e2e.test.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -93
- package/dist/index.js.map +0 -1
- package/dist/slug.d.ts +0 -2
- package/dist/slug.d.ts.map +0 -1
- package/dist/slug.js +0 -12
- package/dist/slug.js.map +0 -1
- package/dist/slug.test.d.ts +0 -2
- package/dist/slug.test.d.ts.map +0 -1
- package/dist/slug.test.js +0 -32
- package/dist/slug.test.js.map +0 -1
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
<overview>
|
|
2
|
+
SwiftData is Apple's modern persistence framework introduced at WWDC 2023, built on Core Data but with a Swift-native API. It provides declarative data modeling, automatic persistence, and seamless SwiftUI integration with minimal boilerplate.
|
|
3
|
+
|
|
4
|
+
**Key insight:** SwiftData eliminates the complexity of Core Data while maintaining its power. Where Core Data requires NSManagedObject subclasses, fetch request controllers, and entity descriptions, SwiftData uses Swift macros (@Model, @Query) and modern Swift features like #Predicate for compile-time validation.
|
|
5
|
+
|
|
6
|
+
**Minimum deployment:** iOS 17, macOS 14, watchOS 10, tvOS 17, visionOS 1.0
|
|
7
|
+
|
|
8
|
+
**When to read this file:**
|
|
9
|
+
- Persisting app data locally or syncing with iCloud
|
|
10
|
+
- Defining data models and relationships
|
|
11
|
+
- Querying and filtering stored data
|
|
12
|
+
- Migrating from Core Data to SwiftData
|
|
13
|
+
- Before reading: architecture.md (understand app structure), state-management.md (understand @Observable)
|
|
14
|
+
- Read alongside: platform-integration.md (for CloudKit integration details)
|
|
15
|
+
</overview>
|
|
16
|
+
|
|
17
|
+
<model_definition>
|
|
18
|
+
## Defining Models
|
|
19
|
+
|
|
20
|
+
**@Model macro:**
|
|
21
|
+
```swift
|
|
22
|
+
import SwiftData
|
|
23
|
+
|
|
24
|
+
@Model
|
|
25
|
+
class Item {
|
|
26
|
+
var name: String
|
|
27
|
+
var timestamp: Date
|
|
28
|
+
var isCompleted: Bool
|
|
29
|
+
|
|
30
|
+
init(name: String) {
|
|
31
|
+
self.name = name
|
|
32
|
+
self.timestamp = Date()
|
|
33
|
+
self.isCompleted = false
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The @Model macro transforms a Swift class into a SwiftData model. SwiftData automatically persists all stored properties.
|
|
39
|
+
|
|
40
|
+
**Supported property types:**
|
|
41
|
+
- Basic types: String, Int, Double, Bool, Date, UUID, URL, Data
|
|
42
|
+
- Codable types (stored as JSON)
|
|
43
|
+
- Collections: [String], [Int], etc.
|
|
44
|
+
- Relationships to other @Model types
|
|
45
|
+
- Optionals of any above type
|
|
46
|
+
|
|
47
|
+
**@Attribute options:**
|
|
48
|
+
```swift
|
|
49
|
+
@Model
|
|
50
|
+
class User {
|
|
51
|
+
@Attribute(.unique) var id: UUID
|
|
52
|
+
@Attribute(.externalStorage) var profileImage: Data
|
|
53
|
+
@Attribute(.spotlight) var displayName: String
|
|
54
|
+
@Attribute(.allowsCloudEncryption) var sensitiveInfo: String
|
|
55
|
+
|
|
56
|
+
var email: String
|
|
57
|
+
|
|
58
|
+
init(id: UUID = UUID(), displayName: String, email: String) {
|
|
59
|
+
self.id = id
|
|
60
|
+
self.displayName = displayName
|
|
61
|
+
self.email = email
|
|
62
|
+
self.profileImage = Data()
|
|
63
|
+
self.sensitiveInfo = ""
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**@Transient for non-persisted properties:**
|
|
69
|
+
```swift
|
|
70
|
+
@Model
|
|
71
|
+
class Task {
|
|
72
|
+
var title: String
|
|
73
|
+
var createdAt: Date
|
|
74
|
+
|
|
75
|
+
@Transient var isEditing: Bool = false
|
|
76
|
+
|
|
77
|
+
var ageInDays: Int {
|
|
78
|
+
Calendar.current.dateComponents([.day], from: createdAt, to: Date()).day ?? 0
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
init(title: String) {
|
|
82
|
+
self.title = title
|
|
83
|
+
self.createdAt = Date()
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
</model_definition>
|
|
88
|
+
|
|
89
|
+
<relationships>
|
|
90
|
+
## Relationships
|
|
91
|
+
|
|
92
|
+
**One-to-many:**
|
|
93
|
+
```swift
|
|
94
|
+
@Model
|
|
95
|
+
class Folder {
|
|
96
|
+
var name: String
|
|
97
|
+
@Relationship(deleteRule: .cascade) var items: [Item] = []
|
|
98
|
+
|
|
99
|
+
init(name: String) {
|
|
100
|
+
self.name = name
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
@Model
|
|
105
|
+
class Item {
|
|
106
|
+
var name: String
|
|
107
|
+
var folder: Folder?
|
|
108
|
+
|
|
109
|
+
init(name: String, folder: Folder? = nil) {
|
|
110
|
+
self.name = name
|
|
111
|
+
self.folder = folder
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Delete rules:**
|
|
117
|
+
- `.cascade` - deletes related objects
|
|
118
|
+
- `.nullify` - sets relationship to nil (default)
|
|
119
|
+
- `.deny` - prevents deletion if relationship exists
|
|
120
|
+
- `.noAction` - does nothing (use with caution)
|
|
121
|
+
|
|
122
|
+
**Inverse relationships:**
|
|
123
|
+
```swift
|
|
124
|
+
@Model
|
|
125
|
+
class Author {
|
|
126
|
+
var name: String
|
|
127
|
+
@Relationship(inverse: \Book.author) var books: [Book] = []
|
|
128
|
+
|
|
129
|
+
init(name: String) {
|
|
130
|
+
self.name = name
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
</relationships>
|
|
135
|
+
|
|
136
|
+
<model_container>
|
|
137
|
+
## ModelContainer and ModelContext
|
|
138
|
+
|
|
139
|
+
**Setting up container in App:**
|
|
140
|
+
```swift
|
|
141
|
+
import SwiftUI
|
|
142
|
+
import SwiftData
|
|
143
|
+
|
|
144
|
+
@main
|
|
145
|
+
struct MyApp: App {
|
|
146
|
+
var body: some Scene {
|
|
147
|
+
WindowGroup {
|
|
148
|
+
ContentView()
|
|
149
|
+
}
|
|
150
|
+
.modelContainer(for: [Item.self, Folder.self])
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Custom configuration:**
|
|
156
|
+
```swift
|
|
157
|
+
let config = ModelConfiguration(
|
|
158
|
+
schema: Schema([Item.self, Folder.self]),
|
|
159
|
+
url: URL.documentsDirectory.appending(path: "MyApp.store"),
|
|
160
|
+
cloudKitDatabase: .automatic
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
let container = try ModelContainer(
|
|
164
|
+
for: Item.self,
|
|
165
|
+
configurations: config
|
|
166
|
+
)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Accessing context in views:**
|
|
170
|
+
```swift
|
|
171
|
+
@Environment(\.modelContext) private var context
|
|
172
|
+
```
|
|
173
|
+
</model_container>
|
|
174
|
+
|
|
175
|
+
<querying>
|
|
176
|
+
## Querying Data
|
|
177
|
+
|
|
178
|
+
**@Query in views:**
|
|
179
|
+
```swift
|
|
180
|
+
@Query var items: [Item]
|
|
181
|
+
|
|
182
|
+
// With sorting
|
|
183
|
+
@Query(sort: \Item.timestamp, order: .reverse) var items: [Item]
|
|
184
|
+
|
|
185
|
+
// With filtering
|
|
186
|
+
@Query(filter: #Predicate<Item> { $0.isCompleted == false }) var items: [Item]
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Dynamic queries:**
|
|
190
|
+
```swift
|
|
191
|
+
struct SearchableItemList: View {
|
|
192
|
+
@Query var items: [Item]
|
|
193
|
+
|
|
194
|
+
init(searchText: String) {
|
|
195
|
+
let predicate = #Predicate<Item> { item in
|
|
196
|
+
searchText.isEmpty || item.name.localizedStandardContains(searchText)
|
|
197
|
+
}
|
|
198
|
+
_items = Query(filter: predicate)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**FetchDescriptor for context queries:**
|
|
204
|
+
```swift
|
|
205
|
+
let descriptor = FetchDescriptor<Item>(
|
|
206
|
+
predicate: #Predicate { $0.isCompleted },
|
|
207
|
+
sortBy: [SortDescriptor(\.timestamp)]
|
|
208
|
+
)
|
|
209
|
+
let items = try context.fetch(descriptor)
|
|
210
|
+
```
|
|
211
|
+
</querying>
|
|
212
|
+
|
|
213
|
+
<crud_operations>
|
|
214
|
+
## CRUD Operations
|
|
215
|
+
|
|
216
|
+
**Create:**
|
|
217
|
+
```swift
|
|
218
|
+
let item = Item(name: "New Item")
|
|
219
|
+
context.insert(item)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Update:**
|
|
223
|
+
```swift
|
|
224
|
+
item.name = "Updated Name"
|
|
225
|
+
// Changes auto-save
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Delete:**
|
|
229
|
+
```swift
|
|
230
|
+
context.delete(item)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**Manual save:**
|
|
234
|
+
```swift
|
|
235
|
+
try context.save()
|
|
236
|
+
```
|
|
237
|
+
</crud_operations>
|
|
238
|
+
|
|
239
|
+
<cloudkit_sync>
|
|
240
|
+
## CloudKit Sync
|
|
241
|
+
|
|
242
|
+
**Enable in container:**
|
|
243
|
+
```swift
|
|
244
|
+
let config = ModelConfiguration(cloudKitDatabase: .automatic)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**CloudKit constraints:**
|
|
248
|
+
- Cannot use @Attribute(.unique) with CloudKit
|
|
249
|
+
- All properties need defaults or be optional
|
|
250
|
+
- Relationships must be optional
|
|
251
|
+
- Private database only
|
|
252
|
+
</cloudkit_sync>
|
|
253
|
+
|
|
254
|
+
<migration>
|
|
255
|
+
## Schema Migration
|
|
256
|
+
|
|
257
|
+
**Lightweight migration (automatic):**
|
|
258
|
+
- Adding properties with defaults
|
|
259
|
+
- Removing properties
|
|
260
|
+
- Renaming with @Attribute(originalName:)
|
|
261
|
+
|
|
262
|
+
**Schema versioning:**
|
|
263
|
+
```swift
|
|
264
|
+
enum SchemaV1: VersionedSchema {
|
|
265
|
+
static var versionIdentifier = Schema.Version(1, 0, 0)
|
|
266
|
+
static var models: [any PersistentModel.Type] { [Item.self] }
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
</migration>
|
|
270
|
+
|
|
271
|
+
<decision_tree>
|
|
272
|
+
## Choosing Your Approach
|
|
273
|
+
|
|
274
|
+
**New project, iOS 17+ only:** SwiftData
|
|
275
|
+
**Need iOS 16 support:** Core Data
|
|
276
|
+
**Existing Core Data project:** Keep Core Data unless full migration planned
|
|
277
|
+
**Need CloudKit:** SwiftData (simpler) or Core Data (more control)
|
|
278
|
+
</decision_tree>
|
|
279
|
+
|
|
280
|
+
<anti_patterns>
|
|
281
|
+
## What NOT to Do
|
|
282
|
+
|
|
283
|
+
<anti_pattern name="Using @Query outside SwiftUI views">
|
|
284
|
+
**Problem:** @Query requires SwiftUI environment
|
|
285
|
+
**Instead:** Use FetchDescriptor with explicit context in view models
|
|
286
|
+
</anti_pattern>
|
|
287
|
+
|
|
288
|
+
<anti_pattern name="Using @Attribute(.unique) with CloudKit">
|
|
289
|
+
**Problem:** Silently breaks CloudKit sync
|
|
290
|
+
**Instead:** Handle uniqueness in app logic
|
|
291
|
+
</anti_pattern>
|
|
292
|
+
|
|
293
|
+
<anti_pattern name="Transient properties in predicates">
|
|
294
|
+
**Problem:** Compiles but crashes at runtime
|
|
295
|
+
**Instead:** Use persisted properties for filtering
|
|
296
|
+
</anti_pattern>
|
|
297
|
+
</anti_patterns>
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
<overview>
|
|
2
|
+
Testing and debugging SwiftUI apps requires a multi-layered approach combining previews, unit tests, UI tests, and debugging tools. SwiftUI's declarative nature makes traditional debugging challenging, but modern tools provide robust solutions.
|
|
3
|
+
|
|
4
|
+
**Key principles:**
|
|
5
|
+
- Use #Preview macros for rapid visual iteration
|
|
6
|
+
- Test business logic with @Observable view models (not views directly)
|
|
7
|
+
- Write focused UI tests using accessibility identifiers
|
|
8
|
+
- Profile with Instruments on real devices
|
|
9
|
+
|
|
10
|
+
SwiftUI views cannot be unit tested directly. Test view models and use UI automation tests for interaction testing.
|
|
11
|
+
</overview>
|
|
12
|
+
|
|
13
|
+
<previews>
|
|
14
|
+
## Xcode Previews
|
|
15
|
+
|
|
16
|
+
**Basic #Preview:**
|
|
17
|
+
```swift
|
|
18
|
+
#Preview {
|
|
19
|
+
ContentView()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#Preview("Dark Mode") {
|
|
23
|
+
ContentView()
|
|
24
|
+
.preferredColorScheme(.dark)
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Multiple states:**
|
|
29
|
+
```swift
|
|
30
|
+
#Preview("Empty") { TaskListView(tasks: []) }
|
|
31
|
+
#Preview("Loaded") { TaskListView(tasks: Task.sampleData) }
|
|
32
|
+
#Preview("Error") { TaskListView(tasks: [], error: "Network unavailable") }
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**With @Binding (Xcode 16+):**
|
|
36
|
+
```swift
|
|
37
|
+
#Preview {
|
|
38
|
+
@Previewable @State var isOn = true
|
|
39
|
+
ToggleView(isOn: $isOn)
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Mock data:**
|
|
44
|
+
```swift
|
|
45
|
+
extension Task {
|
|
46
|
+
static let sampleData: [Task] = [
|
|
47
|
+
Task(title: "Review PR", isCompleted: false),
|
|
48
|
+
Task(title: "Write tests", isCompleted: true)
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
</previews>
|
|
53
|
+
|
|
54
|
+
<unit_testing>
|
|
55
|
+
## Unit Testing View Models
|
|
56
|
+
|
|
57
|
+
**Testing @Observable with Swift Testing:**
|
|
58
|
+
```swift
|
|
59
|
+
import Testing
|
|
60
|
+
@testable import MyApp
|
|
61
|
+
|
|
62
|
+
@Test("Login validation")
|
|
63
|
+
func loginValidation() {
|
|
64
|
+
let viewModel = LoginViewModel()
|
|
65
|
+
viewModel.email = ""
|
|
66
|
+
viewModel.password = "password123"
|
|
67
|
+
#expect(!viewModel.isValidInput)
|
|
68
|
+
|
|
69
|
+
viewModel.email = "user@example.com"
|
|
70
|
+
#expect(viewModel.isValidInput)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@Test("Async data loading")
|
|
74
|
+
func dataLoading() async {
|
|
75
|
+
let mockService = MockService()
|
|
76
|
+
let viewModel = TaskViewModel(service: mockService)
|
|
77
|
+
|
|
78
|
+
await viewModel.load()
|
|
79
|
+
|
|
80
|
+
#expect(!viewModel.tasks.isEmpty)
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Dependency injection for testing:**
|
|
85
|
+
```swift
|
|
86
|
+
@Observable
|
|
87
|
+
final class TaskViewModel {
|
|
88
|
+
private let service: TaskServiceProtocol
|
|
89
|
+
|
|
90
|
+
init(service: TaskServiceProtocol = TaskService()) {
|
|
91
|
+
self.service = service
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
</unit_testing>
|
|
96
|
+
|
|
97
|
+
<ui_testing>
|
|
98
|
+
## UI Testing
|
|
99
|
+
|
|
100
|
+
**Setting accessibility identifiers:**
|
|
101
|
+
```swift
|
|
102
|
+
TextField("Email", text: $email)
|
|
103
|
+
.accessibilityIdentifier("emailField")
|
|
104
|
+
|
|
105
|
+
Button("Login") { }
|
|
106
|
+
.accessibilityIdentifier("loginButton")
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Writing UI tests:**
|
|
110
|
+
```swift
|
|
111
|
+
import XCTest
|
|
112
|
+
|
|
113
|
+
final class LoginUITests: XCTestCase {
|
|
114
|
+
var app: XCUIApplication!
|
|
115
|
+
|
|
116
|
+
override func setUp() {
|
|
117
|
+
continueAfterFailure = false
|
|
118
|
+
app = XCUIApplication()
|
|
119
|
+
app.launch()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
func testLoginFlow() {
|
|
123
|
+
let emailField = app.textFields["emailField"]
|
|
124
|
+
let loginButton = app.buttons["loginButton"]
|
|
125
|
+
|
|
126
|
+
XCTAssertTrue(emailField.waitForExistence(timeout: 5))
|
|
127
|
+
emailField.tap()
|
|
128
|
+
emailField.typeText("user@example.com")
|
|
129
|
+
|
|
130
|
+
loginButton.tap()
|
|
131
|
+
|
|
132
|
+
let welcomeText = app.staticTexts["welcomeMessage"]
|
|
133
|
+
XCTAssertTrue(welcomeText.waitForExistence(timeout: 5))
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
</ui_testing>
|
|
138
|
+
|
|
139
|
+
<debugging>
|
|
140
|
+
## Debugging Techniques
|
|
141
|
+
|
|
142
|
+
**_printChanges():**
|
|
143
|
+
```swift
|
|
144
|
+
var body: some View {
|
|
145
|
+
let _ = Self._printChanges()
|
|
146
|
+
VStack { /* content */ }
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**View hierarchy debugger:**
|
|
151
|
+
Debug menu → View Debugging → Capture View Hierarchy
|
|
152
|
+
|
|
153
|
+
**Lifecycle debugging:**
|
|
154
|
+
```swift
|
|
155
|
+
.onAppear { print("View appeared") }
|
|
156
|
+
.onDisappear { print("View disappeared") }
|
|
157
|
+
.task { print("Task started") }
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Visual debugging:**
|
|
161
|
+
```swift
|
|
162
|
+
.border(.red)
|
|
163
|
+
.background(.yellow.opacity(0.3))
|
|
164
|
+
```
|
|
165
|
+
</debugging>
|
|
166
|
+
|
|
167
|
+
<instruments>
|
|
168
|
+
## Instruments Profiling
|
|
169
|
+
|
|
170
|
+
**SwiftUI template (Xcode 16+):**
|
|
171
|
+
- View Body: Track view creation count
|
|
172
|
+
- View Properties: Monitor property changes
|
|
173
|
+
- Core Animation Commits: Animation performance
|
|
174
|
+
|
|
175
|
+
**Time Profiler:**
|
|
176
|
+
1. Product → Profile (Cmd+I)
|
|
177
|
+
2. Select Time Profiler
|
|
178
|
+
3. Record while using app
|
|
179
|
+
4. Sort by "Self" time to find hotspots
|
|
180
|
+
|
|
181
|
+
**Allocations:**
|
|
182
|
+
- Track memory usage
|
|
183
|
+
- Filter by "Persistent" to find leaks
|
|
184
|
+
|
|
185
|
+
**Always profile on real devices, not simulators.**
|
|
186
|
+
</instruments>
|
|
187
|
+
|
|
188
|
+
<common_bugs>
|
|
189
|
+
## Common SwiftUI Bugs
|
|
190
|
+
|
|
191
|
+
**View not updating:**
|
|
192
|
+
```swift
|
|
193
|
+
// Problem: missing @State
|
|
194
|
+
var count = 0 // Won't trigger updates
|
|
195
|
+
|
|
196
|
+
// Fix: use @State
|
|
197
|
+
@State private var count = 0
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**ForEach crash on empty binding:**
|
|
201
|
+
```swift
|
|
202
|
+
// Problem: binding crashes on empty
|
|
203
|
+
ForEach($items) { $item in }
|
|
204
|
+
|
|
205
|
+
// Fix: check for empty
|
|
206
|
+
if !items.isEmpty {
|
|
207
|
+
ForEach($items) { $item in }
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Animation not working:**
|
|
212
|
+
```swift
|
|
213
|
+
// Problem: no value parameter
|
|
214
|
+
.animation(.spring())
|
|
215
|
+
|
|
216
|
+
// Fix: specify value
|
|
217
|
+
.animation(.spring(), value: isExpanded)
|
|
218
|
+
```
|
|
219
|
+
</common_bugs>
|
|
220
|
+
|
|
221
|
+
<decision_tree>
|
|
222
|
+
## Testing Strategy
|
|
223
|
+
|
|
224
|
+
**Preview:** Visual iteration, different states
|
|
225
|
+
**Unit Test:** @Observable view models, business logic
|
|
226
|
+
**UI Test:** Critical user flows, login, checkout
|
|
227
|
+
**Manual Test:** Animations, accessibility, performance
|
|
228
|
+
</decision_tree>
|
|
229
|
+
|
|
230
|
+
<anti_patterns>
|
|
231
|
+
## What NOT to Do
|
|
232
|
+
|
|
233
|
+
<anti_pattern name="Testing view bodies">
|
|
234
|
+
**Problem:** Trying to unit test SwiftUI views directly
|
|
235
|
+
**Instead:** Extract logic to view models, test those
|
|
236
|
+
</anti_pattern>
|
|
237
|
+
|
|
238
|
+
<anti_pattern name="Missing accessibility identifiers">
|
|
239
|
+
**Problem:** Using text to find elements in UI tests
|
|
240
|
+
**Instead:** Use .accessibilityIdentifier("stableId")
|
|
241
|
+
</anti_pattern>
|
|
242
|
+
|
|
243
|
+
<anti_pattern name="No dependency injection">
|
|
244
|
+
**Problem:** Hardcoded dependencies in view models
|
|
245
|
+
**Instead:** Use protocols, inject mocks in tests
|
|
246
|
+
</anti_pattern>
|
|
247
|
+
</anti_patterns>
|