@kata-sh/cli 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +156 -0
  3. package/dist/app-paths.d.ts +4 -0
  4. package/dist/app-paths.js +6 -0
  5. package/dist/cli.d.ts +1 -0
  6. package/dist/cli.js +56 -0
  7. package/dist/loader.d.ts +2 -0
  8. package/dist/loader.js +95 -0
  9. package/dist/resource-loader.d.ts +18 -0
  10. package/dist/resource-loader.js +50 -0
  11. package/dist/wizard.d.ts +15 -0
  12. package/dist/wizard.js +159 -0
  13. package/package.json +50 -21
  14. package/pkg/dist/modes/interactive/theme/dark.json +85 -0
  15. package/pkg/dist/modes/interactive/theme/light.json +84 -0
  16. package/pkg/dist/modes/interactive/theme/theme-schema.json +335 -0
  17. package/pkg/dist/modes/interactive/theme/theme.d.ts +78 -0
  18. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -0
  19. package/pkg/dist/modes/interactive/theme/theme.js +949 -0
  20. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -0
  21. package/pkg/package.json +8 -0
  22. package/scripts/postinstall.js +45 -0
  23. package/src/resources/AGENTS.md +108 -0
  24. package/src/resources/KATA-WORKFLOW.md +661 -0
  25. package/src/resources/agents/researcher.md +29 -0
  26. package/src/resources/agents/scout.md +56 -0
  27. package/src/resources/agents/worker.md +31 -0
  28. package/src/resources/extensions/ask-user-questions.ts +200 -0
  29. package/src/resources/extensions/bg-shell/index.ts +2758 -0
  30. package/src/resources/extensions/browser-tools/BROWSER-TOOLS-V2-PROPOSAL.md +1277 -0
  31. package/src/resources/extensions/browser-tools/core.js +1057 -0
  32. package/src/resources/extensions/browser-tools/index.ts +4916 -0
  33. package/src/resources/extensions/browser-tools/package.json +20 -0
  34. package/src/resources/extensions/context7/index.ts +428 -0
  35. package/src/resources/extensions/context7/package.json +11 -0
  36. package/src/resources/extensions/get-secrets-from-user.ts +352 -0
  37. package/src/resources/extensions/github/formatters.ts +207 -0
  38. package/src/resources/extensions/github/gh-api.ts +537 -0
  39. package/src/resources/extensions/github/index.ts +778 -0
  40. package/src/resources/extensions/kata/activity-log.ts +88 -0
  41. package/src/resources/extensions/kata/auto.ts +2786 -0
  42. package/src/resources/extensions/kata/commands.ts +355 -0
  43. package/src/resources/extensions/kata/crash-recovery.ts +85 -0
  44. package/src/resources/extensions/kata/dashboard-overlay.ts +516 -0
  45. package/src/resources/extensions/kata/docs/preferences-reference.md +103 -0
  46. package/src/resources/extensions/kata/doctor.ts +683 -0
  47. package/src/resources/extensions/kata/files.ts +730 -0
  48. package/src/resources/extensions/kata/gitignore.ts +165 -0
  49. package/src/resources/extensions/kata/guided-flow.ts +976 -0
  50. package/src/resources/extensions/kata/index.ts +556 -0
  51. package/src/resources/extensions/kata/metrics.ts +397 -0
  52. package/src/resources/extensions/kata/observability-validator.ts +408 -0
  53. package/src/resources/extensions/kata/package.json +11 -0
  54. package/src/resources/extensions/kata/paths.ts +346 -0
  55. package/src/resources/extensions/kata/preferences.ts +695 -0
  56. package/src/resources/extensions/kata/prompt-loader.ts +50 -0
  57. package/src/resources/extensions/kata/prompts/complete-milestone.md +25 -0
  58. package/src/resources/extensions/kata/prompts/complete-slice.md +27 -0
  59. package/src/resources/extensions/kata/prompts/discuss.md +151 -0
  60. package/src/resources/extensions/kata/prompts/doctor-heal.md +29 -0
  61. package/src/resources/extensions/kata/prompts/execute-task.md +64 -0
  62. package/src/resources/extensions/kata/prompts/guided-complete-slice.md +1 -0
  63. package/src/resources/extensions/kata/prompts/guided-discuss-milestone.md +3 -0
  64. package/src/resources/extensions/kata/prompts/guided-discuss-slice.md +59 -0
  65. package/src/resources/extensions/kata/prompts/guided-execute-task.md +1 -0
  66. package/src/resources/extensions/kata/prompts/guided-plan-milestone.md +23 -0
  67. package/src/resources/extensions/kata/prompts/guided-plan-slice.md +1 -0
  68. package/src/resources/extensions/kata/prompts/guided-research-slice.md +11 -0
  69. package/src/resources/extensions/kata/prompts/guided-resume-task.md +1 -0
  70. package/src/resources/extensions/kata/prompts/plan-milestone.md +47 -0
  71. package/src/resources/extensions/kata/prompts/plan-slice.md +63 -0
  72. package/src/resources/extensions/kata/prompts/queue.md +85 -0
  73. package/src/resources/extensions/kata/prompts/reassess-roadmap.md +48 -0
  74. package/src/resources/extensions/kata/prompts/replan-slice.md +39 -0
  75. package/src/resources/extensions/kata/prompts/research-milestone.md +37 -0
  76. package/src/resources/extensions/kata/prompts/research-slice.md +28 -0
  77. package/src/resources/extensions/kata/prompts/run-uat.md +109 -0
  78. package/src/resources/extensions/kata/prompts/system.md +341 -0
  79. package/src/resources/extensions/kata/session-forensics.ts +550 -0
  80. package/src/resources/extensions/kata/skill-discovery.ts +137 -0
  81. package/src/resources/extensions/kata/state.ts +509 -0
  82. package/src/resources/extensions/kata/templates/context.md +76 -0
  83. package/src/resources/extensions/kata/templates/decisions.md +8 -0
  84. package/src/resources/extensions/kata/templates/milestone-summary.md +73 -0
  85. package/src/resources/extensions/kata/templates/plan.md +133 -0
  86. package/src/resources/extensions/kata/templates/preferences.md +15 -0
  87. package/src/resources/extensions/kata/templates/project.md +31 -0
  88. package/src/resources/extensions/kata/templates/reassessment.md +28 -0
  89. package/src/resources/extensions/kata/templates/requirements.md +81 -0
  90. package/src/resources/extensions/kata/templates/research.md +46 -0
  91. package/src/resources/extensions/kata/templates/roadmap.md +118 -0
  92. package/src/resources/extensions/kata/templates/slice-context.md +58 -0
  93. package/src/resources/extensions/kata/templates/slice-summary.md +99 -0
  94. package/src/resources/extensions/kata/templates/state.md +19 -0
  95. package/src/resources/extensions/kata/templates/task-plan.md +52 -0
  96. package/src/resources/extensions/kata/templates/task-summary.md +57 -0
  97. package/src/resources/extensions/kata/templates/uat.md +54 -0
  98. package/src/resources/extensions/kata/tests/activity-log-prune.test.ts +327 -0
  99. package/src/resources/extensions/kata/tests/auto-preflight.test.ts +97 -0
  100. package/src/resources/extensions/kata/tests/auto-supervisor.test.mjs +53 -0
  101. package/src/resources/extensions/kata/tests/complete-milestone.test.ts +317 -0
  102. package/src/resources/extensions/kata/tests/cost-projection.test.ts +160 -0
  103. package/src/resources/extensions/kata/tests/derive-state-deps.test.ts +477 -0
  104. package/src/resources/extensions/kata/tests/derive-state.test.ts +1013 -0
  105. package/src/resources/extensions/kata/tests/doctor.test.ts +718 -0
  106. package/src/resources/extensions/kata/tests/idle-recovery.test.ts +490 -0
  107. package/src/resources/extensions/kata/tests/metrics-io.test.ts +254 -0
  108. package/src/resources/extensions/kata/tests/metrics.test.ts +217 -0
  109. package/src/resources/extensions/kata/tests/must-have-parser.test.ts +309 -0
  110. package/src/resources/extensions/kata/tests/parsers.test.ts +1257 -0
  111. package/src/resources/extensions/kata/tests/plan-milestone.test.ts +185 -0
  112. package/src/resources/extensions/kata/tests/plan-quality-validator.test.ts +386 -0
  113. package/src/resources/extensions/kata/tests/reassess-prompt.test.ts +208 -0
  114. package/src/resources/extensions/kata/tests/replan-slice.test.ts +686 -0
  115. package/src/resources/extensions/kata/tests/requirements.test.ts +151 -0
  116. package/src/resources/extensions/kata/tests/resolve-ts-hooks.mjs +17 -0
  117. package/src/resources/extensions/kata/tests/resolve-ts.mjs +11 -0
  118. package/src/resources/extensions/kata/tests/run-uat.test.ts +383 -0
  119. package/src/resources/extensions/kata/tests/unit-runtime.test.ts +388 -0
  120. package/src/resources/extensions/kata/tests/workspace-index.test.ts +118 -0
  121. package/src/resources/extensions/kata/tests/worktree.test.ts +222 -0
  122. package/src/resources/extensions/kata/types.ts +159 -0
  123. package/src/resources/extensions/kata/unit-runtime.ts +163 -0
  124. package/src/resources/extensions/kata/workspace-index.ts +203 -0
  125. package/src/resources/extensions/kata/worktree.ts +182 -0
  126. package/src/resources/extensions/mac-tools/index.ts +852 -0
  127. package/src/resources/extensions/mac-tools/swift-cli/Package.swift +22 -0
  128. package/src/resources/extensions/mac-tools/swift-cli/Sources/main.swift +1318 -0
  129. package/src/resources/extensions/search-the-web/cache.ts +78 -0
  130. package/src/resources/extensions/search-the-web/format.ts +258 -0
  131. package/src/resources/extensions/search-the-web/http.ts +238 -0
  132. package/src/resources/extensions/search-the-web/index.ts +68 -0
  133. package/src/resources/extensions/search-the-web/tool-fetch-page.ts +519 -0
  134. package/src/resources/extensions/search-the-web/tool-llm-context.ts +404 -0
  135. package/src/resources/extensions/search-the-web/tool-search.ts +503 -0
  136. package/src/resources/extensions/search-the-web/url-utils.ts +91 -0
  137. package/src/resources/extensions/shared/confirm-ui.ts +126 -0
  138. package/src/resources/extensions/shared/interview-ui.ts +822 -0
  139. package/src/resources/extensions/shared/next-action-ui.ts +235 -0
  140. package/src/resources/extensions/shared/progress-widget.ts +282 -0
  141. package/src/resources/extensions/shared/thinking-widget.ts +107 -0
  142. package/src/resources/extensions/shared/ui.ts +400 -0
  143. package/src/resources/extensions/shared/wizard-ui.ts +551 -0
  144. package/src/resources/extensions/slash-commands/audit.ts +92 -0
  145. package/src/resources/extensions/slash-commands/create-extension.ts +375 -0
  146. package/src/resources/extensions/slash-commands/create-slash-command.ts +280 -0
  147. package/src/resources/extensions/slash-commands/index.ts +12 -0
  148. package/src/resources/extensions/slash-commands/kata-run.ts +34 -0
  149. package/src/resources/extensions/subagent/agents.ts +126 -0
  150. package/src/resources/extensions/subagent/index.ts +1293 -0
  151. package/src/resources/skills/debug-like-expert/SKILL.md +231 -0
  152. package/src/resources/skills/debug-like-expert/references/debugging-mindset.md +253 -0
  153. package/src/resources/skills/debug-like-expert/references/hypothesis-testing.md +373 -0
  154. package/src/resources/skills/debug-like-expert/references/investigation-techniques.md +337 -0
  155. package/src/resources/skills/debug-like-expert/references/verification-patterns.md +425 -0
  156. package/src/resources/skills/debug-like-expert/references/when-to-research.md +361 -0
  157. package/src/resources/skills/frontend-design/SKILL.md +45 -0
  158. package/src/resources/skills/swiftui/SKILL.md +208 -0
  159. package/src/resources/skills/swiftui/references/animations.md +921 -0
  160. package/src/resources/skills/swiftui/references/architecture.md +1561 -0
  161. package/src/resources/skills/swiftui/references/layout-system.md +1186 -0
  162. package/src/resources/skills/swiftui/references/navigation.md +1492 -0
  163. package/src/resources/skills/swiftui/references/networking-async.md +214 -0
  164. package/src/resources/skills/swiftui/references/performance.md +1706 -0
  165. package/src/resources/skills/swiftui/references/platform-integration.md +204 -0
  166. package/src/resources/skills/swiftui/references/state-management.md +1443 -0
  167. package/src/resources/skills/swiftui/references/swiftdata.md +297 -0
  168. package/src/resources/skills/swiftui/references/testing-debugging.md +247 -0
  169. package/src/resources/skills/swiftui/references/uikit-appkit-interop.md +218 -0
  170. package/src/resources/skills/swiftui/workflows/add-feature.md +191 -0
  171. package/src/resources/skills/swiftui/workflows/build-new-app.md +311 -0
  172. package/src/resources/skills/swiftui/workflows/debug-swiftui.md +192 -0
  173. package/src/resources/skills/swiftui/workflows/optimize-performance.md +197 -0
  174. package/src/resources/skills/swiftui/workflows/ship-app.md +203 -0
  175. package/src/resources/skills/swiftui/workflows/write-tests.md +235 -0
  176. package/dist/commands/task.d.ts +0 -9
  177. package/dist/commands/task.d.ts.map +0 -1
  178. package/dist/commands/task.js +0 -129
  179. package/dist/commands/task.js.map +0 -1
  180. package/dist/commands/task.test.d.ts +0 -2
  181. package/dist/commands/task.test.d.ts.map +0 -1
  182. package/dist/commands/task.test.js +0 -169
  183. package/dist/commands/task.test.js.map +0 -1
  184. package/dist/e2e/task-e2e.test.d.ts +0 -2
  185. package/dist/e2e/task-e2e.test.d.ts.map +0 -1
  186. package/dist/e2e/task-e2e.test.js +0 -173
  187. package/dist/e2e/task-e2e.test.js.map +0 -1
  188. package/dist/index.d.ts +0 -3
  189. package/dist/index.d.ts.map +0 -1
  190. package/dist/index.js +0 -93
  191. package/dist/index.js.map +0 -1
  192. package/dist/slug.d.ts +0 -2
  193. package/dist/slug.d.ts.map +0 -1
  194. package/dist/slug.js +0 -12
  195. package/dist/slug.js.map +0 -1
  196. package/dist/slug.test.d.ts +0 -2
  197. package/dist/slug.test.d.ts.map +0 -1
  198. package/dist/slug.test.js +0 -32
  199. package/dist/slug.test.js.map +0 -1
@@ -0,0 +1,921 @@
1
+ <overview>
2
+ SwiftUI animations are declarative and state-driven. When state changes, SwiftUI automatically animates views from old to new values. Your role is to control timing curves, duration, and which state changes trigger animations.
3
+
4
+ Key insight: Animations are automatic when state changes - you control timing/curve, not the mechanics.
5
+
6
+ This file covers:
7
+ - Implicit vs explicit animations
8
+ - Spring animations (iOS 17+ duration/bounce API)
9
+ - Transitions for appearing/disappearing views
10
+ - matchedGeometryEffect for hero animations
11
+ - PhaseAnimator and KeyframeAnimator (iOS 17+)
12
+ - Gesture-driven animations
13
+
14
+ See also:
15
+ - navigation.md for NavigationStack transitions
16
+ - performance.md for animation optimization strategies
17
+ </overview>
18
+
19
+ <implicit_animations>
20
+ ## Implicit Animations (.animation modifier)
21
+
22
+ Implicit animations apply whenever an animatable property changes on a view. Always specify which value triggers the animation using the `value:` parameter to prevent unexpected animations.
23
+
24
+ **Basic usage:**
25
+ ```swift
26
+ struct ContentView: View {
27
+ @State private var scale: CGFloat = 1.0
28
+
29
+ var body: some View {
30
+ Circle()
31
+ .fill(.blue)
32
+ .scaleEffect(scale)
33
+ .animation(.spring(), value: scale)
34
+ .onTapGesture {
35
+ scale = scale == 1.0 ? 1.5 : 1.0
36
+ }
37
+ }
38
+ }
39
+ ```
40
+
41
+ **Animation types:**
42
+ - `.default` - System default spring animation
43
+ - `.linear(duration:)` - Constant speed from start to finish
44
+ - `.easeIn(duration:)` - Starts slow, accelerates
45
+ - `.easeOut(duration:)` - Starts fast, decelerates
46
+ - `.easeInOut(duration:)` - Slow start and end, fast middle
47
+ - `.spring()` - iOS 17+ spring with default parameters
48
+ - `.bouncy` - Preset spring with high bounce
49
+ - `.snappy` - Preset spring with quick, slight bounce
50
+ - `.smooth` - Preset spring with no bounce
51
+
52
+ **Value-specific animation:**
53
+ ```swift
54
+ struct MultiPropertyView: View {
55
+ @State private var rotation: Double = 0
56
+ @State private var scale: CGFloat = 1.0
57
+
58
+ var body: some View {
59
+ Rectangle()
60
+ .fill(.red)
61
+ .scaleEffect(scale)
62
+ .rotationEffect(.degrees(rotation))
63
+ .animation(.spring(), value: rotation) // Only animate rotation
64
+ .animation(.easeInOut, value: scale) // Different animation for scale
65
+ }
66
+ }
67
+ ```
68
+
69
+ **Why always use value: parameter:**
70
+ - Prevents unexpected animations on unrelated state changes
71
+ - Device rotation won't trigger animations
72
+ - More predictable behavior
73
+ - Better performance (only tracks specific value)
74
+ </implicit_animations>
75
+
76
+ <explicit_animations>
77
+ ## Explicit Animations (withAnimation)
78
+
79
+ Explicit animations only affect properties that depend on values changed inside the `withAnimation` closure. Preferred for user-triggered actions.
80
+
81
+ **Basic usage:**
82
+ ```swift
83
+ struct ContentView: View {
84
+ @State private var isExpanded = false
85
+
86
+ var body: some View {
87
+ VStack {
88
+ if isExpanded {
89
+ Text("Details")
90
+ .transition(.opacity)
91
+ }
92
+
93
+ Button("Toggle") {
94
+ withAnimation(.spring()) {
95
+ isExpanded.toggle()
96
+ }
97
+ }
98
+ }
99
+ }
100
+ }
101
+ ```
102
+
103
+ **Completion handlers (iOS 17+):**
104
+ ```swift
105
+ Button("Animate") {
106
+ withAnimation(.easeInOut(duration: 1.0)) {
107
+ offset.y = 200
108
+ } completion: {
109
+ // Animation finished - safe to perform next action
110
+ showNextStep = true
111
+ }
112
+ }
113
+ ```
114
+
115
+ **Transaction-based:**
116
+ ```swift
117
+ var transaction = Transaction(animation: .spring())
118
+ transaction.disablesAnimations = true // Temporarily disable animations
119
+
120
+ withTransaction(transaction) {
121
+ someState.toggle()
122
+ }
123
+ ```
124
+
125
+ **Removing animations temporarily:**
126
+ ```swift
127
+ withAnimation(nil) {
128
+ // Changes happen immediately without animation
129
+ resetState()
130
+ }
131
+ ```
132
+ </explicit_animations>
133
+
134
+ <spring_animations>
135
+ ## Spring Animations
136
+
137
+ Springs are the default animation in SwiftUI. They feel natural because they mimic real-world physics.
138
+
139
+ **Modern spring parameters (iOS 17+):**
140
+ ```swift
141
+ // Duration and bounce control
142
+ .spring(duration: 0.5, bounce: 0.3)
143
+
144
+ // No bounce with blend duration for smooth transitions
145
+ .spring(duration: 0.5, bounce: 0, blendDuration: 0.2)
146
+
147
+ // With initial velocity for gesture-driven animations
148
+ .spring(duration: 0.6, bounce: 0.4)
149
+ ```
150
+
151
+ **Bounce parameter:**
152
+ - `-1.0` to `1.0` range
153
+ - `0` = no bounce (critically damped)
154
+ - `0.3` to `0.5` = natural bounce
155
+ - `0.7` to `1.0` = exaggerated bounce
156
+ - Negative values create "anticipation" (overshoots in opposite direction first)
157
+
158
+ **Presets (iOS 17+):**
159
+ ```swift
160
+ .bouncy // High bounce - playful, attention-grabbing
161
+ .snappy // Quick with slight bounce - feels responsive
162
+ .smooth // No bounce - elegant, sophisticated
163
+ ```
164
+
165
+ **Tuning workflow:**
166
+ 1. Start with duration that feels right
167
+ 2. Adjust bounce to set character/feeling
168
+ 3. Use presets first, then customize if needed
169
+
170
+ **Legacy spring (still works):**
171
+ ```swift
172
+ // For backward compatibility or precise control
173
+ .spring(response: 0.5, dampingFraction: 0.7, blendDuration: 0)
174
+ ```
175
+
176
+ **When to use springs:**
177
+ - User interactions (button presses, drags)
178
+ - Most UI state changes
179
+ - Default choice unless you need precise timing
180
+ </spring_animations>
181
+
182
+ <transitions>
183
+ ## Transitions
184
+
185
+ Transitions control how views appear and disappear. Applied with `.transition()` modifier, animated by wrapping insertion/removal in `withAnimation`.
186
+
187
+ **Built-in transitions:**
188
+ ```swift
189
+ struct TransitionsDemo: View {
190
+ @State private var showDetail = false
191
+
192
+ var body: some View {
193
+ VStack {
194
+ if showDetail {
195
+ Text("Detail")
196
+ .transition(.opacity) // Fade in/out
197
+ // .transition(.slide) // Slide from leading edge
198
+ // .transition(.scale) // Grow/shrink from center
199
+ // .transition(.move(edge: .bottom)) // Slide from bottom
200
+ // .transition(.push(from: .leading)) // Push from leading (iOS 16+)
201
+ }
202
+
203
+ Button("Toggle") {
204
+ withAnimation {
205
+ showDetail.toggle()
206
+ }
207
+ }
208
+ }
209
+ }
210
+ }
211
+ ```
212
+
213
+ **Combining transitions:**
214
+ ```swift
215
+ // Both opacity and scale together
216
+ .transition(.opacity.combined(with: .scale))
217
+
218
+ // Different insertion and removal
219
+ .transition(.asymmetric(
220
+ insertion: .move(edge: .leading).combined(with: .opacity),
221
+ removal: .move(edge: .trailing).combined(with: .opacity)
222
+ ))
223
+ ```
224
+
225
+ **Custom transitions:**
226
+ ```swift
227
+ struct RotateModifier: ViewModifier {
228
+ let rotation: Double
229
+
230
+ func body(content: Content) -> some View {
231
+ content
232
+ .rotationEffect(.degrees(rotation))
233
+ .opacity(rotation == 0 ? 1 : 0)
234
+ }
235
+ }
236
+
237
+ extension AnyTransition {
238
+ static var pivot: AnyTransition {
239
+ .modifier(
240
+ active: RotateModifier(rotation: -90),
241
+ identity: RotateModifier(rotation: 0)
242
+ )
243
+ }
244
+ }
245
+
246
+ // Usage
247
+ Text("Pivoting in")
248
+ .transition(.pivot)
249
+ ```
250
+
251
+ **Identity vs insertion/removal:**
252
+ - `identity` = final state when view is visible
253
+ - `active` = state during transition (appearing/disappearing)
254
+ </transitions>
255
+
256
+ <matched_geometry>
257
+ ## matchedGeometryEffect
258
+
259
+ Synchronizes geometry between two views with the same ID, creating hero animations. Views don't need to be in the same container.
260
+
261
+ **Basic hero animation:**
262
+ ```swift
263
+ struct HeroDemo: View {
264
+ @State private var isExpanded = false
265
+ @Namespace private var animation
266
+
267
+ var body: some View {
268
+ VStack {
269
+ if !isExpanded {
270
+ // Thumbnail state
271
+ Circle()
272
+ .fill(.blue)
273
+ .frame(width: 60, height: 60)
274
+ .matchedGeometryEffect(id: "circle", in: animation)
275
+ .onTapGesture {
276
+ withAnimation(.spring()) {
277
+ isExpanded = true
278
+ }
279
+ }
280
+ } else {
281
+ // Expanded state
282
+ VStack {
283
+ Circle()
284
+ .fill(.blue)
285
+ .frame(width: 200, height: 200)
286
+ .matchedGeometryEffect(id: "circle", in: animation)
287
+
288
+ Button("Close") {
289
+ withAnimation(.spring()) {
290
+ isExpanded = false
291
+ }
292
+ }
293
+ }
294
+ }
295
+ }
296
+ }
297
+ }
298
+ ```
299
+
300
+ **Creating namespace:**
301
+ ```swift
302
+ @Namespace private var animation // Property wrapper creates unique namespace
303
+ ```
304
+
305
+ **isSource parameter:**
306
+ Controls which view provides geometry during transition.
307
+
308
+ ```swift
309
+ // Example: Grid to detail view
310
+ struct ContentView: View {
311
+ @State private var selectedItem: Item?
312
+ @Namespace private var namespace
313
+
314
+ var body: some View {
315
+ ZStack {
316
+ // Grid view
317
+ LazyVGrid(columns: columns) {
318
+ ForEach(items) { item in
319
+ ItemCard(item: item)
320
+ .matchedGeometryEffect(
321
+ id: item.id,
322
+ in: namespace,
323
+ isSource: selectedItem == nil // Source when detail not shown
324
+ )
325
+ .onTapGesture {
326
+ selectedItem = item
327
+ }
328
+ }
329
+ }
330
+
331
+ // Detail view
332
+ if let item = selectedItem {
333
+ DetailView(item: item)
334
+ .matchedGeometryEffect(
335
+ id: item.id,
336
+ in: namespace,
337
+ isSource: selectedItem != nil // Source when detail shown
338
+ )
339
+ }
340
+ }
341
+ .animation(.spring(), value: selectedItem)
342
+ }
343
+ }
344
+ ```
345
+
346
+ **Properties parameter:**
347
+ Control what gets matched.
348
+
349
+ ```swift
350
+ .matchedGeometryEffect(
351
+ id: "shape",
352
+ in: namespace,
353
+ properties: .frame // Only match frame, not position
354
+ )
355
+
356
+ // Options: .frame, .position, .size
357
+ ```
358
+
359
+ **Common pitfalls:**
360
+ - **Both views must exist simultaneously** during animation - use conditional rendering carefully
361
+ - **Same ID required** - use stable identifiers (UUIDs, database IDs)
362
+ - **Need explicit animation** - wrap state changes in `withAnimation`
363
+ - **ZStack coordination** - often need ZStack to ensure both views render during transition
364
+ </matched_geometry>
365
+
366
+ <phased_animations>
367
+ ## Phased Animations (iOS 17+)
368
+
369
+ PhaseAnimator automatically cycles through animation phases. Ideal for loading indicators, attention-grabbing effects, or multi-step sequences.
370
+
371
+ **PhaseAnimator with continuous cycling:**
372
+ ```swift
373
+ struct PulsingCircle: View {
374
+ var body: some View {
375
+ PhaseAnimator([false, true]) { isLarge in
376
+ Circle()
377
+ .fill(.red)
378
+ .scaleEffect(isLarge ? 1.5 : 1.0)
379
+ .opacity(isLarge ? 0.5 : 1.0)
380
+ } animation: { phase in
381
+ .easeInOut(duration: 1.0)
382
+ }
383
+ }
384
+ }
385
+ ```
386
+
387
+ **PhaseAnimator with enum phases:**
388
+ ```swift
389
+ enum LoadingPhase: CaseIterable {
390
+ case initial, loading, success
391
+
392
+ var scale: CGFloat {
393
+ switch self {
394
+ case .initial: 1.0
395
+ case .loading: 1.2
396
+ case .success: 1.5
397
+ }
398
+ }
399
+
400
+ var color: Color {
401
+ switch self {
402
+ case .initial: .gray
403
+ case .loading: .blue
404
+ case .success: .green
405
+ }
406
+ }
407
+ }
408
+
409
+ struct LoadingButton: View {
410
+ var body: some View {
411
+ PhaseAnimator(LoadingPhase.allCases) { phase in
412
+ Circle()
413
+ .fill(phase.color)
414
+ .scaleEffect(phase.scale)
415
+ } animation: { phase in
416
+ switch phase {
417
+ case .initial: .easeIn(duration: 0.3)
418
+ case .loading: .easeInOut(duration: 0.5)
419
+ case .success: .spring(duration: 0.6, bounce: 0.4)
420
+ }
421
+ }
422
+ }
423
+ }
424
+ ```
425
+
426
+ **Trigger-based PhaseAnimator:**
427
+ ```swift
428
+ struct TriggerDemo: View {
429
+ @State private var triggerValue = 0
430
+
431
+ var body: some View {
432
+ VStack {
433
+ PhaseAnimator([0, 1, 2], trigger: triggerValue) { phase in
434
+ RoundedRectangle(cornerRadius: 12)
435
+ .fill(.blue)
436
+ .frame(width: 100 + CGFloat(phase * 50), height: 100)
437
+ .offset(x: CGFloat(phase * 20))
438
+ }
439
+
440
+ Button("Animate") {
441
+ triggerValue += 1
442
+ }
443
+ }
444
+ }
445
+ }
446
+ ```
447
+
448
+ **Use cases:**
449
+ - Loading spinners and progress indicators
450
+ - Attention-grabbing call-to-action buttons
451
+ - Celebratory success animations
452
+ - Idle state animations
453
+ - Tutorial highlights
454
+ </phased_animations>
455
+
456
+ <keyframe_animations>
457
+ ## Keyframe Animations (iOS 17+)
458
+
459
+ KeyframeAnimator provides frame-by-frame control over complex animations. More powerful than PhaseAnimator when you need precise timing and multiple simultaneous property changes.
460
+
461
+ **Basic KeyframeAnimator:**
462
+ ```swift
463
+ struct AnimationValues {
464
+ var scale = 1.0
465
+ var rotation = 0.0
466
+ var opacity = 1.0
467
+ }
468
+
469
+ struct KeyframeDemo: View {
470
+ @State private var trigger = false
471
+
472
+ var body: some View {
473
+ KeyframeAnimator(
474
+ initialValue: AnimationValues(),
475
+ trigger: trigger
476
+ ) { values in
477
+ Rectangle()
478
+ .fill(.purple)
479
+ .scaleEffect(values.scale)
480
+ .rotationEffect(.degrees(values.rotation))
481
+ .opacity(values.opacity)
482
+ .frame(width: 100, height: 100)
483
+ } keyframes: { _ in
484
+ KeyframeTrack(\.scale) {
485
+ SpringKeyframe(1.5, duration: 0.3)
486
+ CubicKeyframe(0.8, duration: 0.2)
487
+ CubicKeyframe(1.0, duration: 0.2)
488
+ }
489
+
490
+ KeyframeTrack(\.rotation) {
491
+ LinearKeyframe(180, duration: 0.4)
492
+ CubicKeyframe(360, duration: 0.3)
493
+ }
494
+
495
+ KeyframeTrack(\.opacity) {
496
+ CubicKeyframe(0.5, duration: 0.3)
497
+ CubicKeyframe(1.0, duration: 0.4)
498
+ }
499
+ }
500
+ .onTapGesture {
501
+ trigger.toggle()
502
+ }
503
+ }
504
+ }
505
+ ```
506
+
507
+ **Keyframe types:**
508
+
509
+ ```swift
510
+ // Linear - constant speed interpolation
511
+ LinearKeyframe(targetValue, duration: 0.5)
512
+
513
+ // Cubic - smooth Bezier curve
514
+ CubicKeyframe(targetValue, duration: 0.5)
515
+
516
+ // Spring - physics-based bounce
517
+ SpringKeyframe(targetValue, duration: 0.5, spring: .bouncy)
518
+
519
+ // Move - jump immediately to value
520
+ MoveKeyframe(targetValue)
521
+ ```
522
+
523
+ **Complex multi-property animation:**
524
+ ```swift
525
+ struct AnimationState {
526
+ var position: CGPoint = .zero
527
+ var color: Color = .blue
528
+ var size: CGFloat = 50
529
+ }
530
+
531
+ KeyframeAnimator(initialValue: AnimationState(), trigger: animate) { state in
532
+ Circle()
533
+ .fill(state.color)
534
+ .frame(width: state.size, height: state.size)
535
+ .position(state.position)
536
+ } keyframes: { _ in
537
+ KeyframeTrack(\.position) {
538
+ CubicKeyframe(CGPoint(x: 200, y: 100), duration: 0.4)
539
+ SpringKeyframe(CGPoint(x: 200, y: 300), duration: 0.6)
540
+ CubicKeyframe(CGPoint(x: 0, y: 0), duration: 0.5)
541
+ }
542
+
543
+ KeyframeTrack(\.color) {
544
+ CubicKeyframe(.red, duration: 0.5)
545
+ CubicKeyframe(.green, duration: 0.5)
546
+ CubicKeyframe(.blue, duration: 0.5)
547
+ }
548
+
549
+ KeyframeTrack(\.size) {
550
+ SpringKeyframe(100, duration: 0.6, spring: .bouncy)
551
+ CubicKeyframe(50, duration: 0.4)
552
+ }
553
+ }
554
+ ```
555
+
556
+ **When to use KeyframeAnimator:**
557
+ - Complex choreographed animations
558
+ - Precise timing control needed
559
+ - Multiple properties animating with different curves
560
+ - Path-based animations
561
+ - Recreating motion design prototypes
562
+ </keyframe_animations>
563
+
564
+ <gesture_animations>
565
+ ## Gesture-Driven Animations
566
+
567
+ Interactive animations that respond to user input in real-time.
568
+
569
+ **DragGesture with spring animation:**
570
+ ```swift
571
+ struct DraggableCard: View {
572
+ @State private var offset: CGSize = .zero
573
+
574
+ var body: some View {
575
+ RoundedRectangle(cornerRadius: 20)
576
+ .fill(.blue)
577
+ .frame(width: 200, height: 300)
578
+ .offset(offset)
579
+ .gesture(
580
+ DragGesture()
581
+ .onChanged { value in
582
+ offset = value.translation
583
+ }
584
+ .onEnded { _ in
585
+ withAnimation(.spring(duration: 0.5, bounce: 0.3)) {
586
+ offset = .zero
587
+ }
588
+ }
589
+ )
590
+ }
591
+ }
592
+ ```
593
+
594
+ **Interruptible animations:**
595
+ ```swift
596
+ struct InterruptibleView: View {
597
+ @State private var position: CGFloat = 0
598
+
599
+ var body: some View {
600
+ Circle()
601
+ .fill(.red)
602
+ .frame(width: 60, height: 60)
603
+ .offset(y: position)
604
+ .animation(.spring(), value: position)
605
+ .gesture(
606
+ DragGesture()
607
+ .onChanged { value in
608
+ // Interrupts ongoing animation immediately
609
+ position = value.translation.height
610
+ }
611
+ .onEnded { value in
612
+ // Determine snap point based on velocity
613
+ let velocity = value.predictedEndLocation.y - value.location.y
614
+
615
+ if abs(velocity) > 500 {
616
+ position = velocity > 0 ? 300 : -300
617
+ } else {
618
+ position = 0
619
+ }
620
+ }
621
+ )
622
+ }
623
+ }
624
+ ```
625
+
626
+ **GestureState for automatic reset:**
627
+ ```swift
628
+ struct GestureStateExample: View {
629
+ @GestureState private var dragOffset: CGSize = .zero
630
+ @State private var permanentOffset: CGSize = .zero
631
+
632
+ var body: some View {
633
+ Rectangle()
634
+ .fill(.purple)
635
+ .frame(width: 100, height: 100)
636
+ .offset(x: permanentOffset.width + dragOffset.width,
637
+ y: permanentOffset.height + dragOffset.height)
638
+ .gesture(
639
+ DragGesture()
640
+ .updating($dragOffset) { value, state, _ in
641
+ state = value.translation
642
+ }
643
+ .onEnded { value in
644
+ withAnimation(.spring()) {
645
+ permanentOffset.width += value.translation.width
646
+ permanentOffset.height += value.translation.height
647
+ }
648
+ }
649
+ )
650
+ }
651
+ }
652
+ ```
653
+
654
+ **Combining gestures with animations:**
655
+ ```swift
656
+ struct SwipeToDelete: View {
657
+ @State private var offset: CGFloat = 0
658
+ @State private var isDeleted = false
659
+
660
+ var body: some View {
661
+ if !isDeleted {
662
+ HStack {
663
+ Text("Swipe to delete")
664
+ Spacer()
665
+ }
666
+ .padding()
667
+ .background(.white)
668
+ .offset(x: offset)
669
+ .gesture(
670
+ DragGesture()
671
+ .onChanged { value in
672
+ if value.translation.width < 0 {
673
+ offset = value.translation.width
674
+ }
675
+ }
676
+ .onEnded { value in
677
+ if offset < -100 {
678
+ withAnimation(.easeOut(duration: 0.3)) {
679
+ offset = -500
680
+ } completion: {
681
+ isDeleted = true
682
+ }
683
+ } else {
684
+ withAnimation(.spring()) {
685
+ offset = 0
686
+ }
687
+ }
688
+ }
689
+ )
690
+ }
691
+ }
692
+ }
693
+ ```
694
+
695
+ **Velocity-based animations:**
696
+ ```swift
697
+ struct VelocityDrag: View {
698
+ @State private var offset: CGSize = .zero
699
+
700
+ var body: some View {
701
+ Circle()
702
+ .fill(.green)
703
+ .frame(width: 80, height: 80)
704
+ .offset(offset)
705
+ .gesture(
706
+ DragGesture()
707
+ .onChanged { value in
708
+ offset = value.translation
709
+ }
710
+ .onEnded { value in
711
+ let velocity = value.velocity
712
+
713
+ // Use velocity magnitude to determine spring response
714
+ let speed = sqrt(velocity.width * velocity.width +
715
+ velocity.height * velocity.height)
716
+
717
+ let animation: Animation = speed > 1000
718
+ ? .spring(duration: 0.4, bounce: 0.5)
719
+ : .spring(duration: 0.6, bounce: 0.3)
720
+
721
+ withAnimation(animation) {
722
+ offset = .zero
723
+ }
724
+ }
725
+ )
726
+ }
727
+ }
728
+ ```
729
+ </gesture_animations>
730
+
731
+ <decision_tree>
732
+ ## Choosing the Right Animation
733
+
734
+ **Simple state change:**
735
+ - Use `.animation(.default, value: state)` for single property changes
736
+ - Implicit animation is simplest approach
737
+
738
+ **User-triggered change:**
739
+ - Use `withAnimation { }` for button taps, user actions
740
+ - Explicit animation provides better control
741
+ - Use completion handlers (iOS 17+) for sequential actions
742
+
743
+ **View appearing/disappearing:**
744
+ - Use `.transition()` for conditional views
745
+ - Combine with `withAnimation` to trigger
746
+ - Consider `.asymmetric()` for different in/out animations
747
+
748
+ **Shared element between screens:**
749
+ - Use `matchedGeometryEffect` for hero animations
750
+ - Requires both views to exist during transition
751
+ - Best with `@Namespace` and explicit animations
752
+
753
+ **Multi-step sequence:**
754
+ - Use `PhaseAnimator` (iOS 17+) for simple phase-based sequences
755
+ - Great for loading states, idle animations
756
+ - Trigger-based for user-initiated sequences
757
+
758
+ **Complex keyframed motion:**
759
+ - Use `KeyframeAnimator` (iOS 17+) for precise timing
760
+ - Multiple properties with independent curves
761
+ - Recreating motion design specs
762
+
763
+ **User-controlled motion:**
764
+ - Use `DragGesture` + animation for interactive elements
765
+ - `@GestureState` for automatic state reset
766
+ - Consider velocity for natural physics
767
+
768
+ **Performance tips:**
769
+ - Animate opacity, scale, offset (cheap)
770
+ - Avoid animating frame size, padding (expensive)
771
+ - Use `.drawingGroup()` for complex hierarchies being animated
772
+ - Avoid animating during scroll (competes with scroll performance)
773
+ - Profile with Instruments if animations drop frames
774
+ </decision_tree>
775
+
776
+ <anti_patterns>
777
+ ## What NOT to Do
778
+
779
+ <anti_pattern name="Animation without value parameter">
780
+ **Problem:**
781
+ ```swift
782
+ .animation(.spring()) // No value parameter
783
+ ```
784
+
785
+ **Why it's bad:**
786
+ Animates every property change, including device rotation, parent view updates, and unrelated state changes. Creates unexpected animations and performance issues.
787
+
788
+ **Instead:**
789
+ ```swift
790
+ .animation(.spring(), value: specificState)
791
+ ```
792
+ </anti_pattern>
793
+
794
+ <anti_pattern name="Animating layout-heavy properties">
795
+ **Problem:**
796
+ ```swift
797
+ withAnimation {
798
+ frameWidth = 300 // Triggers layout recalculation
799
+ padding = 20 // Triggers layout recalculation
800
+ }
801
+ ```
802
+
803
+ **Why it's bad:**
804
+ Frame size and padding changes force SwiftUI to recalculate layout, which is expensive. Can cause stuttering on complex views.
805
+
806
+ **Instead:**
807
+ ```swift
808
+ withAnimation {
809
+ scale = 1.5 // Cheap transform
810
+ opacity = 0.5 // Cheap property
811
+ offset = CGSize(width: 20, height: 0) // Cheap transform
812
+ }
813
+ ```
814
+ </anti_pattern>
815
+
816
+ <anti_pattern name="matchedGeometryEffect without namespace">
817
+ **Problem:**
818
+ ```swift
819
+ Circle()
820
+ .matchedGeometryEffect(id: "circle", in: ???) // Forgot @Namespace
821
+ ```
822
+
823
+ **Why it's bad:**
824
+ Won't compile. Namespace is required to coordinate geometry matching.
825
+
826
+ **Instead:**
827
+ ```swift
828
+ @Namespace private var animation
829
+
830
+ Circle()
831
+ .matchedGeometryEffect(id: "circle", in: animation)
832
+ ```
833
+ </anti_pattern>
834
+
835
+ <anti_pattern name="Nested withAnimation blocks">
836
+ **Problem:**
837
+ ```swift
838
+ withAnimation(.easeIn) {
839
+ withAnimation(.spring()) {
840
+ state = newValue
841
+ }
842
+ }
843
+ ```
844
+
845
+ **Why it's bad:**
846
+ Inner animation is ignored. Only outer animation applies. Creates confusion about which animation runs.
847
+
848
+ **Instead:**
849
+ ```swift
850
+ withAnimation(.spring()) {
851
+ state = newValue
852
+ }
853
+ ```
854
+ </anti_pattern>
855
+
856
+ <anti_pattern name="Transition without withAnimation">
857
+ **Problem:**
858
+ ```swift
859
+ if showDetail {
860
+ DetailView()
861
+ .transition(.slide) // Transition defined but not triggered
862
+ }
863
+ ```
864
+
865
+ **Why it's bad:**
866
+ View appears/disappears instantly. Transition is never applied without animation context.
867
+
868
+ **Instead:**
869
+ ```swift
870
+ Button("Toggle") {
871
+ withAnimation {
872
+ showDetail.toggle()
873
+ }
874
+ }
875
+ ```
876
+ </anti_pattern>
877
+
878
+ <anti_pattern name="Animating computed properties">
879
+ **Problem:**
880
+ ```swift
881
+ var computedValue: Double {
882
+ return stateA * stateB
883
+ }
884
+
885
+ .animation(.spring(), value: computedValue)
886
+ ```
887
+
888
+ **Why it's bad:**
889
+ Computed properties can change for many reasons. Animation triggers on any dependency change, not just intentional updates.
890
+
891
+ **Instead:**
892
+ ```swift
893
+ .animation(.spring(), value: stateA)
894
+ .animation(.spring(), value: stateB)
895
+ ```
896
+ </anti_pattern>
897
+
898
+ <anti_pattern name="matchedGeometryEffect with overlapping views">
899
+ **Problem:**
900
+ ```swift
901
+ // Both views exist at same time with same ID
902
+ GridItem()
903
+ .matchedGeometryEffect(id: item.id, in: namespace)
904
+
905
+ DetailItem()
906
+ .matchedGeometryEffect(id: item.id, in: namespace)
907
+ ```
908
+
909
+ **Why it's bad:**
910
+ Without proper `isSource` configuration, SwiftUI doesn't know which view's geometry to use. Creates unpredictable animations.
911
+
912
+ **Instead:**
913
+ ```swift
914
+ GridItem()
915
+ .matchedGeometryEffect(id: item.id, in: namespace, isSource: selectedItem == nil)
916
+
917
+ DetailItem()
918
+ .matchedGeometryEffect(id: item.id, in: namespace, isSource: selectedItem != nil)
919
+ ```
920
+ </anti_pattern>
921
+ </anti_patterns>