engsys 1.0.0

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 (173) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +202 -0
  3. package/core/agents/aaron.md +152 -0
  4. package/core/agents/bert.md +115 -0
  5. package/core/agents/isabelle.md +136 -0
  6. package/core/agents/jody.md +150 -0
  7. package/core/agents/leith.md +111 -0
  8. package/core/agents/marcelo.md +282 -0
  9. package/core/agents/melvin.md +101 -0
  10. package/core/agents/nyx.md +152 -0
  11. package/core/agents/otto.md +168 -0
  12. package/core/agents/patricia.md +283 -0
  13. package/core/commands/design-audit-local.md +155 -0
  14. package/core/commands/design-audit.md +235 -0
  15. package/core/commands/design-critique.md +96 -0
  16. package/core/commands/file-issue.md +22 -0
  17. package/core/commands/generate-project.md +45 -0
  18. package/core/commands/implement-issue.md +37 -0
  19. package/core/commands/implement-project.md +40 -0
  20. package/core/commands/naturalize.md +61 -0
  21. package/core/commands/pre-push.md +29 -0
  22. package/core/commands/prep-review-collect.md +130 -0
  23. package/core/commands/prep-review-finalize.md +121 -0
  24. package/core/commands/prep-review-publish.md +113 -0
  25. package/core/commands/prep-review.md +65 -0
  26. package/core/commands/project-closeout.md +25 -0
  27. package/core/skills/agentic-eval/SKILL.md +195 -0
  28. package/core/skills/chrome-devtools/SKILL.md +97 -0
  29. package/core/skills/code-review/SKILL.md +26 -0
  30. package/core/skills/gh-cli/SKILL.md +2202 -0
  31. package/core/skills/git-commit/SKILL.md +124 -0
  32. package/core/skills/git-workflow-agents/SKILL.md +462 -0
  33. package/core/skills/git-workflow-agents/reference.md +220 -0
  34. package/core/skills/github-actions/SKILL.md +190 -0
  35. package/core/skills/github-issues/SKILL.md +154 -0
  36. package/core/skills/llm-structured-outputs/SKILL.md +323 -0
  37. package/core/skills/llm-structured-outputs/references/provider-details.md +392 -0
  38. package/core/skills/pre-push/SKILL.md +115 -0
  39. package/core/skills/refactor/SKILL.md +645 -0
  40. package/core/skills/web-design-reviewer/SKILL.md +371 -0
  41. package/core/skills/webapp-testing/SKILL.md +127 -0
  42. package/core/skills/webapp-testing/test-helper.js +56 -0
  43. package/core/templates/CLAUDE.md.tmpl +98 -0
  44. package/core/templates/adr-template.md +67 -0
  45. package/core/templates/gh-issue-templates/bug.md +39 -0
  46. package/core/templates/gh-issue-templates/content.md +42 -0
  47. package/core/templates/gh-issue-templates/enhancement.md +36 -0
  48. package/core/templates/gh-issue-templates/feature.md +39 -0
  49. package/core/templates/gh-issue-templates/infrastructure.md +41 -0
  50. package/core/templates/post-edit-reminders.sh.tmpl +19 -0
  51. package/core/templates/settings.json.tmpl +90 -0
  52. package/core/templates/settings.local.json.tmpl +3 -0
  53. package/core/workflows/agent-implementation-workflow.md +346 -0
  54. package/core/workflows/generate-project.md +258 -0
  55. package/core/workflows/implement-project-workflow.md +190 -0
  56. package/core/workflows/issue-tracking.md +89 -0
  57. package/core/workflows/project-closeout-ceremony.md +77 -0
  58. package/core/workflows/review-workflow.md +266 -0
  59. package/engsys.config.example.yaml +46 -0
  60. package/install +202 -0
  61. package/lessons-library/README.md +80 -0
  62. package/lessons-library/async-callbacks-verify-liveness.md +15 -0
  63. package/lessons-library/change-isnt-done-until-every-surface-updated.md +15 -0
  64. package/lessons-library/claim-then-act-for-irreversible-ops.md +16 -0
  65. package/lessons-library/co-commit-entangled-work.md +15 -0
  66. package/lessons-library/dependabot-triage-playbook.md +17 -0
  67. package/lessons-library/deploy-by-digest-and-verify-the-running-revision.md +15 -0
  68. package/lessons-library/enforce-your-guarantee-at-your-boundary.md +16 -0
  69. package/lessons-library/gate-changes-on-measurement-not-vibes.md +15 -0
  70. package/lessons-library/iac-first-no-console-changes.md +15 -0
  71. package/lessons-library/independent-objective-review-gate.md +15 -0
  72. package/lessons-library/keep-an-immutable-source-of-truth.md +15 -0
  73. package/lessons-library/long-agent-runs-checkpoint-not-poll.md +15 -0
  74. package/lessons-library/model-identity-with-stable-ids-and-provenance.md +15 -0
  75. package/lessons-library/operator-choices-are-first-class.md +15 -0
  76. package/lessons-library/prefer-tool-enforced-structured-output.md +15 -0
  77. package/lessons-library/prove-causation-before-acting.md +15 -0
  78. package/lessons-library/re-read-state-before-acting.md +14 -0
  79. package/lessons-library/read-layer-tolerates-unbackfilled-rows.md +15 -0
  80. package/lessons-library/shell-safety-pipefail-and-validate-before-teardown.md +14 -0
  81. package/lessons-library/shift-correctness-left-and-distrust-false-greens.md +15 -0
  82. package/lessons-library/stray-control-bytes-hide-changes.md +14 -0
  83. package/lessons-library/tests-can-assert-the-bug.md +15 -0
  84. package/lessons-library/verify-ground-truth-not-reports.md +15 -0
  85. package/lessons-library/worktrees-need-bootstrap-from-origin-main.md +15 -0
  86. package/lib/commands.js +356 -0
  87. package/lib/generate-team-avatars.mjs +251 -0
  88. package/lib/manifest.js +155 -0
  89. package/lib/render.js +135 -0
  90. package/lib/selftest.js +90 -0
  91. package/lib/util.js +89 -0
  92. package/lib/yaml.js +156 -0
  93. package/optional-agents/gary.md +86 -0
  94. package/optional-agents/jos.md +136 -0
  95. package/optional-agents/sandy.md +101 -0
  96. package/optional-agents/steve.md +161 -0
  97. package/package.json +43 -0
  98. package/stacks/cloud/aws/claude.fragment.md +17 -0
  99. package/stacks/cloud/aws/settings.fragment.json +39 -0
  100. package/stacks/cloud/aws/skills/aws-deployment-preflight/SKILL.md +165 -0
  101. package/stacks/cloud/aws/skills/cloud-architecture-aws/SKILL.md +265 -0
  102. package/stacks/cloud/azure/claude.fragment.md +17 -0
  103. package/stacks/cloud/azure/settings.fragment.json +45 -0
  104. package/stacks/cloud/azure/skills/azure-deployment-preflight/SKILL.md +175 -0
  105. package/stacks/cloud/azure/skills/cloud-architecture-azure/SKILL.md +211 -0
  106. package/stacks/cloud/cloudflare/claude.fragment.md +21 -0
  107. package/stacks/cloud/cloudflare/settings.fragment.json +31 -0
  108. package/stacks/cloud/cloudflare/skills/cloud-architecture-cloudflare/SKILL.md +294 -0
  109. package/stacks/cloud/cloudflare/skills/cloudflare-deployment-preflight/SKILL.md +175 -0
  110. package/stacks/cloud/gcp/claude.fragment.md +17 -0
  111. package/stacks/cloud/gcp/settings.fragment.json +40 -0
  112. package/stacks/cloud/gcp/skills/cloud-architecture-gcp/SKILL.md +208 -0
  113. package/stacks/cloud/gcp/skills/gcp-deployment-preflight/SKILL.md +137 -0
  114. package/stacks/db/mongo/skills/mongo-conventions/SKILL.md +96 -0
  115. package/stacks/db/prisma/claude.fragment.md +49 -0
  116. package/stacks/db/prisma/skills/docker-database-package-copy/SKILL.md +44 -0
  117. package/stacks/db/prisma/skills/prisma-conventions/SKILL.md +37 -0
  118. package/stacks/domain/mobile-growth/skills/apple-ads/SKILL.md +184 -0
  119. package/stacks/domain/mobile-growth/skills/apple-ads/references/benchmark-notes.md +47 -0
  120. package/stacks/domain/mobile-growth/skills/apple-ads/references/official-links.md +53 -0
  121. package/stacks/domain/mobile-growth/skills/google-play-growth/SKILL.md +197 -0
  122. package/stacks/domain/mobile-growth/skills/google-play-growth/references/benchmark-notes.md +47 -0
  123. package/stacks/domain/mobile-growth/skills/google-play-growth/references/official-links.md +45 -0
  124. package/stacks/iac/bicep/claude.fragment.md +14 -0
  125. package/stacks/iac/bicep/settings.fragment.json +20 -0
  126. package/stacks/iac/bicep/skills/iac-bicep/SKILL.md +113 -0
  127. package/stacks/iac/cdk/claude.fragment.md +14 -0
  128. package/stacks/iac/cdk/settings.fragment.json +23 -0
  129. package/stacks/iac/cdk/skills/iac-cdk/SKILL.md +104 -0
  130. package/stacks/iac/terraform/claude.fragment.md +13 -0
  131. package/stacks/iac/terraform/settings.fragment.json +25 -0
  132. package/stacks/iac/terraform/skills/iac-terraform/SKILL.md +93 -0
  133. package/stacks/iac/terraform/skills/terraform-conventions/SKILL.md +87 -0
  134. package/stacks/lang/kotlin/skills/android-testing/SKILL.md +263 -0
  135. package/stacks/lang/kotlin/skills/jetpack-compose/SKILL.md +264 -0
  136. package/stacks/lang/kotlin/skills/kotlin-coroutines/SKILL.md +329 -0
  137. package/stacks/lang/python/skills/python-conventions/SKILL.md +61 -0
  138. package/stacks/lang/shell/skills/shell-scripting/SKILL.md +110 -0
  139. package/stacks/lang/swift/skills/swift-concurrency/SKILL.md +423 -0
  140. package/stacks/lang/swift/skills/swift-concurrency/references/approachable-concurrency.md +80 -0
  141. package/stacks/lang/swift/skills/swift-concurrency/references/concurrency-patterns.md +233 -0
  142. package/stacks/lang/swift/skills/swift-concurrency/references/swiftui-concurrency.md +187 -0
  143. package/stacks/lang/swift/skills/swift-concurrency/references/synchronization-primitives.md +341 -0
  144. package/stacks/lang/swift/skills/swift-testing/SKILL.md +497 -0
  145. package/stacks/lang/swift/skills/swift-testing/references/testing-advanced.md +106 -0
  146. package/stacks/lang/swift/skills/swift-testing/references/testing-patterns.md +504 -0
  147. package/stacks/lang/swift/skills/swiftdata/SKILL.md +334 -0
  148. package/stacks/lang/swift/skills/swiftdata/references/core-data-coexistence.md +504 -0
  149. package/stacks/lang/swift/skills/swiftdata/references/swiftdata-advanced.md +975 -0
  150. package/stacks/lang/swift/skills/swiftdata/references/swiftdata-queries.md +675 -0
  151. package/stacks/lang/swift/skills/swiftui-patterns/SKILL.md +371 -0
  152. package/stacks/lang/swift/skills/swiftui-patterns/references/architecture-patterns.md +486 -0
  153. package/stacks/lang/swift/skills/swiftui-patterns/references/deprecated-migration.md +1097 -0
  154. package/stacks/lang/swift/skills/swiftui-patterns/references/design-polish.md +780 -0
  155. package/stacks/lang/swift/skills/swiftui-patterns/references/platform-and-sharing.md +696 -0
  156. package/stacks/lang/typescript/skills/typescript-conventions/SKILL.md +91 -0
  157. package/stacks/platform/android/claude.fragment.md +40 -0
  158. package/stacks/platform/android/hooks/pre-push-gradle.sh +70 -0
  159. package/stacks/platform/android/settings.fragment.json +13 -0
  160. package/stacks/platform/android/skills/android-build-conventions/SKILL.md +247 -0
  161. package/stacks/platform/ios/claude.fragment.md +24 -0
  162. package/stacks/platform/ios/hooks/pre-push-xcodebuild.sh +82 -0
  163. package/stacks/platform/ios/settings.fragment.json +21 -0
  164. package/stacks/platform/ios/skills/xcodebuildmcp-simulator-logs/SKILL.md +76 -0
  165. package/stacks/platform/web/skills/frontend-testing/SKILL.md +246 -0
  166. package/stacks/platform/web/skills/react-conventions/SKILL.md +261 -0
  167. package/stacks/platform/web/skills/web-platform-conventions/SKILL.md +55 -0
  168. package/stacks/tooling/issue-tracker-github/claude.fragment.md +10 -0
  169. package/stacks/tooling/issue-tracker-github/settings.fragment.json +24 -0
  170. package/stacks/tooling/issue-tracker-github/skills/issue-tracker-github/SKILL.md +278 -0
  171. package/stacks/tooling/issue-tracker-linear/claude.fragment.md +17 -0
  172. package/stacks/tooling/issue-tracker-linear/settings.fragment.json +9 -0
  173. package/stacks/tooling/issue-tracker-linear/skills/issue-tracker-linear/SKILL.md +183 -0
@@ -0,0 +1,371 @@
1
+ ---
2
+ name: swiftui-patterns
3
+ description: "Build SwiftUI views with modern MV architecture, state management, and view composition patterns. Covers @Observable ownership rules, @State/@Bindable/@Environment wiring, view decomposition, custom ViewModifiers, environment values, async data loading with .task, iOS 26+ APIs, Writing Tools, and performance guidelines. Use when structuring a SwiftUI app, managing state with @Observable, composing view hierarchies, or applying SwiftUI best practices."
4
+ ---
5
+
6
+ # SwiftUI Patterns
7
+
8
+ Modern SwiftUI patterns targeting iOS 26+ with Swift 6.3. Covers architecture, state management, view composition, environment wiring, async loading, design polish, and platform/share integration. Navigation and layout patterns live in dedicated sibling skills. Patterns are backward-compatible to iOS 17 unless noted.
9
+
10
+ ## Contents
11
+
12
+ - [Architecture: Model-View (MV) Pattern](#architecture-model-view-mv-pattern)
13
+ - [State Management](#state-management)
14
+ - [View Ordering Convention](#view-ordering-convention)
15
+ - [View Composition](#view-composition)
16
+ - [Environment](#environment)
17
+ - [Async Data Loading](#async-data-loading)
18
+ - [iOS 26+ New APIs](#ios-26-new-apis)
19
+ - [Performance Guidelines](#performance-guidelines)
20
+ - [HIG Alignment](#hig-alignment)
21
+ - [Writing Tools (iOS 18+)](#writing-tools-ios-18)
22
+ - [Common Mistakes](#common-mistakes)
23
+ - [Review Checklist](#review-checklist)
24
+ - [References](#references)
25
+
26
+ **Scope boundary:** This skill covers architecture, state ownership, composition, environment wiring, async loading, and related SwiftUI app structure patterns. Detailed navigation patterns are covered in the `swiftui-navigation` skill, including `NavigationStack`, `NavigationSplitView`, sheets, tabs, and deep-linking patterns. Detailed layout, container, and component patterns are covered in the `swiftui-layout-components` skill, including stacks, grids, lists, scroll view patterns, forms, controls, search UI with `.searchable`, overlays, and related layout components.
27
+
28
+ ## Architecture: Model-View (MV) Pattern
29
+
30
+ Default to MV -- views are lightweight state expressions; models and services own business logic. Do not introduce view models unless the existing code already uses them.
31
+
32
+ **Core principles:**
33
+ - Favor `@State`, `@Environment`, `@Query`, `.task`, and `.onChange` for orchestration
34
+ - Inject services and shared models via `@Environment`; keep views small and composable
35
+ - Split large views into smaller subviews rather than introducing a view model
36
+ - Test models, services, and business logic; keep views simple and declarative
37
+
38
+ ```swift
39
+ struct FeedView: View {
40
+ @Environment(FeedClient.self) private var client
41
+
42
+ enum ViewState {
43
+ case loading, error(String), loaded([Post])
44
+ }
45
+
46
+ @State private var viewState: ViewState = .loading
47
+
48
+ var body: some View {
49
+ List {
50
+ switch viewState {
51
+ case .loading:
52
+ ProgressView()
53
+ case .error(let message):
54
+ ContentUnavailableView("Error", systemImage: "exclamationmark.triangle",
55
+ description: Text(message))
56
+ case .loaded(let posts):
57
+ ForEach(posts) { post in
58
+ PostRow(post: post)
59
+ }
60
+ }
61
+ }
62
+ .task { await loadFeed() }
63
+ .refreshable { await loadFeed() }
64
+ }
65
+
66
+ private func loadFeed() async {
67
+ do {
68
+ let posts = try await client.getFeed()
69
+ viewState = .loaded(posts)
70
+ } catch {
71
+ viewState = .error(error.localizedDescription)
72
+ }
73
+ }
74
+ }
75
+ ```
76
+
77
+ For MV pattern rationale, app wiring, and lightweight client examples, see [references/architecture-patterns.md](references/architecture-patterns.md).
78
+
79
+ ## State Management
80
+
81
+ ### @Observable Ownership Rules
82
+
83
+ **Important:** Always annotate `@Observable` view model classes with `@MainActor` to ensure UI-bound state is updated on the main thread. Required for Swift 6 concurrency safety.
84
+
85
+ | Wrapper | When to Use |
86
+ |---------|-------------|
87
+ | `@State` | View owns the object or value. Creates and manages lifecycle. |
88
+ | `let` | View receives an `@Observable` object. Read-only observation -- no wrapper needed. |
89
+ | `@Bindable` | View receives an `@Observable` object and needs two-way bindings (`$property`). |
90
+ | `@Environment(Type.self)` | Access shared `@Observable` object from environment. |
91
+ | `@State` (value types) | View-local simple state: toggles, counters, text field values. Always `private`. |
92
+ | `@Binding` | Two-way connection to parent's `@State` or `@Bindable` property. |
93
+
94
+ ### Ownership Pattern
95
+
96
+ ```swift
97
+ // @Observable view model -- always @MainActor
98
+ @MainActor
99
+ @Observable final class ItemStore {
100
+ var title = ""
101
+ var items: [Item] = []
102
+ }
103
+
104
+ // View that OWNS the model
105
+ struct ParentView: View {
106
+ @State var viewModel = ItemStore()
107
+
108
+ var body: some View {
109
+ ChildView(store: viewModel)
110
+ .environment(viewModel)
111
+ }
112
+ }
113
+
114
+ // View that READS (no wrapper needed for @Observable)
115
+ struct ChildView: View {
116
+ let store: ItemStore
117
+
118
+ var body: some View { Text(store.title) }
119
+ }
120
+
121
+ // View that BINDS (needs two-way access)
122
+ struct EditView: View {
123
+ @Bindable var store: ItemStore
124
+
125
+ var body: some View {
126
+ TextField("Title", text: $store.title)
127
+ }
128
+ }
129
+
130
+ // View that reads from ENVIRONMENT
131
+ struct DeepView: View {
132
+ @Environment(ItemStore.self) var store
133
+
134
+ var body: some View {
135
+ @Bindable var s = store
136
+ TextField("Title", text: $s.title)
137
+ }
138
+ }
139
+ ```
140
+
141
+ **Granular tracking:** SwiftUI only re-renders views that read properties that changed. If a view reads `items` but not `isLoading`, changing `isLoading` does not trigger a re-render. This is a major performance advantage over `ObservableObject`.
142
+
143
+ ### Legacy ObservableObject
144
+
145
+ Only use if supporting iOS 16 or earlier. `@StateObject` → `@State`, `@ObservedObject` → `let`, `@EnvironmentObject` → `@Environment(Type.self)`.
146
+
147
+ ## View Ordering Convention
148
+
149
+ Order members top to bottom: 1) `@Environment` 2) `let` properties 3) `@State` / stored properties 4) computed `var` 5) `init` 6) `body` 7) view builders / helpers 8) async functions
150
+
151
+ ## View Composition
152
+
153
+ ### Extract Subviews
154
+
155
+ Break views into focused subviews. Each should have a single responsibility.
156
+
157
+ ```swift
158
+ var body: some View {
159
+ VStack {
160
+ HeaderSection(title: title, isPinned: isPinned)
161
+ DetailsSection(details: details)
162
+ ActionsSection(onSave: onSave, onCancel: onCancel)
163
+ }
164
+ }
165
+ ```
166
+
167
+ ### Computed View Properties
168
+
169
+ Keep related subviews as computed properties in the same file; extract to a standalone `View` struct when reuse is intended or the subview carries its own state.
170
+
171
+ ```swift
172
+ var body: some View {
173
+ List {
174
+ header
175
+ filters
176
+ results
177
+ }
178
+ }
179
+
180
+ private var header: some View {
181
+ VStack(alignment: .leading, spacing: 6) {
182
+ Text(title).font(.title2)
183
+ Text(subtitle).font(.subheadline)
184
+ }
185
+ }
186
+ ```
187
+
188
+ ### ViewBuilder Functions
189
+
190
+ For conditional logic that does not warrant a separate struct:
191
+
192
+ ```swift
193
+ @ViewBuilder
194
+ private func statusBadge(for status: Status) -> some View {
195
+ switch status {
196
+ case .active: Text("Active").foregroundStyle(.green)
197
+ case .inactive: Text("Inactive").foregroundStyle(.secondary)
198
+ }
199
+ }
200
+ ```
201
+
202
+ ### Custom View Modifiers
203
+
204
+ Extract repeated styling into `ViewModifier`:
205
+
206
+ ```swift
207
+ struct CardStyle: ViewModifier {
208
+ func body(content: Content) -> some View {
209
+ content
210
+ .padding()
211
+ .background(.background)
212
+ .clipShape(RoundedRectangle(cornerRadius: 12))
213
+ .shadow(radius: 2)
214
+ }
215
+ }
216
+ extension View { func cardStyle() -> some View { modifier(CardStyle()) } }
217
+ ```
218
+
219
+ ### Stable View Tree
220
+
221
+ Avoid top-level conditional view swapping. Prefer a single stable base view with conditions inside sections or modifiers. When a view file exceeds ~300 lines, split with extensions and `// MARK: -` comments.
222
+
223
+ ## Environment
224
+
225
+ ### Custom Environment Values
226
+
227
+ ```swift
228
+ private struct ThemeKey: EnvironmentKey {
229
+ static let defaultValue: Theme = .default
230
+ }
231
+
232
+ extension EnvironmentValues {
233
+ var theme: Theme {
234
+ get { self[ThemeKey.self] }
235
+ set { self[ThemeKey.self] = newValue }
236
+ }
237
+ }
238
+
239
+ // Usage
240
+ .environment(\.theme, customTheme)
241
+ @Environment(\.theme) var theme
242
+ ```
243
+
244
+ ### Common Built-in Environment Values
245
+
246
+ ```swift
247
+ @Environment(\.dismiss) var dismiss
248
+ @Environment(\.colorScheme) var colorScheme
249
+ @Environment(\.dynamicTypeSize) var dynamicTypeSize
250
+ @Environment(\.horizontalSizeClass) var sizeClass
251
+ @Environment(\.isSearching) var isSearching
252
+ @Environment(\.openURL) var openURL
253
+ @Environment(\.modelContext) var modelContext
254
+ ```
255
+
256
+ ## Async Data Loading
257
+
258
+ Always use `.task` -- it cancels automatically on view disappear:
259
+
260
+ ```swift
261
+ struct ItemListView: View {
262
+ @State var store = ItemStore()
263
+
264
+ var body: some View {
265
+ List(store.items) { item in
266
+ ItemRow(item: item)
267
+ }
268
+ .task { await store.load() }
269
+ .refreshable { await store.refresh() }
270
+ }
271
+ }
272
+ ```
273
+
274
+ Use `.task(id:)` to re-run when a dependency changes:
275
+
276
+ ```swift
277
+ .task(id: searchText) {
278
+ guard !searchText.isEmpty else { return }
279
+ await search(query: searchText)
280
+ }
281
+ ```
282
+
283
+ Never create manual `Task` in `onAppear` unless you need to store a reference for cancellation. Exception: `Task {}` is acceptable in synchronous action closures (e.g., Button actions) for immediate state updates before async work.
284
+
285
+ ## iOS 26+ New APIs
286
+
287
+ - **`.scrollEdgeEffectStyle(.soft, for: .top)`** -- fading edge effect on scroll edges
288
+ - **`.backgroundExtensionEffect()`** -- mirror/blur at safe area edges
289
+ - **`@Animatable`** macro -- synthesizes `AnimatableData` conformance automatically (see `swiftui-animation` skill)
290
+ - **`TextEditor`** -- now accepts `AttributedString` for rich text
291
+
292
+ ## Performance Guidelines
293
+
294
+ - **Lazy stacks/grids:** Use `LazyVStack`, `LazyHStack`, `LazyVGrid`, `LazyHGrid` for large collections. Regular stacks render all children immediately.
295
+ - **Stable IDs:** All items in `List`/`ForEach` must conform to `Identifiable` with stable IDs. Never use array indices.
296
+ - **Avoid body recomputation:** Move filtering and sorting to computed properties or the model, not inline in `body`.
297
+ - **Equatable views:** For complex views that re-render unnecessarily, conform to `Equatable`.
298
+
299
+ ## HIG Alignment
300
+
301
+ Follow Apple Human Interface Guidelines for layout, typography, color, and accessibility. Key rules:
302
+
303
+ - Use semantic colors (`Color.primary`, `.secondary`, `Color(uiColor: .systemBackground)`) for automatic light/dark mode
304
+ - Use system font styles (`.title`, `.headline`, `.body`, `.caption`) for Dynamic Type support
305
+ - Use `ContentUnavailableView` for empty and error states
306
+ - Support adaptive layouts via `horizontalSizeClass`
307
+ - Provide VoiceOver labels (`.accessibilityLabel`) and support Dynamic Type accessibility sizes by switching layout orientation
308
+
309
+ See [references/design-polish.md](references/design-polish.md) for HIG, theming, haptics, focus, transitions, and loading patterns.
310
+
311
+ ## Writing Tools (iOS 18+)
312
+
313
+ Control the Apple Intelligence Writing Tools experience on text views with `.writingToolsBehavior(_:)`.
314
+
315
+ | Level | Effect | When to use |
316
+ |-------|--------|-------------|
317
+ | `.complete` | Full inline rewriting (proofread, rewrite, transform) | Notes, email, documents |
318
+ | `.limited` | Overlay panel only — original text untouched | Code editors, validated forms |
319
+ | `.disabled` | Writing Tools hidden entirely | Passwords, search bars |
320
+ | `.automatic` | System chooses based on context (default) | Most views |
321
+
322
+ ```swift
323
+ TextEditor(text: $body)
324
+ .writingToolsBehavior(.complete)
325
+ TextField("Search…", text: $query)
326
+ .writingToolsBehavior(.disabled)
327
+ ```
328
+
329
+ **Detecting active sessions:** Read `isWritingToolsActive` on `UITextView` (UIKit) to defer validation or suspend undo grouping until a rewrite finishes.
330
+
331
+ > **Docs:** [WritingToolsBehavior](https://sosumi.ai/documentation/swiftui/writingtoolsbehavior) · [writingToolsBehavior(_:)](https://sosumi.ai/documentation/swiftui/view/writingtoolsbehavior(_:))
332
+
333
+ ## Common Mistakes
334
+
335
+ 1. Using `@ObservedObject` to create objects -- use `@StateObject` (legacy) or `@State` (modern)
336
+ 2. Heavy computation in view `body` -- move to model or computed property
337
+ 3. Not using `.task` for async work -- manual `Task` in `onAppear` leaks if not cancelled
338
+ 4. Array indices as `ForEach` IDs -- causes incorrect diffing and UI bugs
339
+ 5. Forgetting `@Bindable` -- `$property` syntax on `@Observable` requires `@Bindable`
340
+ 6. Over-using `@State` -- only for view-local state; shared state belongs in `@Observable`
341
+ 7. Not extracting subviews -- long body blocks are hard to read and optimize
342
+ 8. Using `NavigationView` -- deprecated; use `NavigationStack`
343
+ 9. Inline closures in body -- extract complex closures to methods
344
+ 10. `.sheet(isPresented:)` when state represents a model -- use `.sheet(item:)` instead
345
+ 11. **Using `AnyView` for type erasure** -- causes identity resets and disables diffing. Use `@ViewBuilder`, `Group`, or generics instead. See [references/deprecated-migration.md](references/deprecated-migration.md)
346
+
347
+ ## Review Checklist
348
+
349
+ - [ ] `@Observable` used for shared state models (not `ObservableObject` on iOS 17+)
350
+ - [ ] `@State` owns objects; `let`/`@Bindable` receives them
351
+ - [ ] `NavigationStack` used (not `NavigationView`)
352
+ - [ ] `.task` modifier for async data loading
353
+ - [ ] `LazyVStack`/`LazyHStack` for large collections
354
+ - [ ] Stable `Identifiable` IDs (not array indices)
355
+ - [ ] Views decomposed into focused subviews
356
+ - [ ] No heavy computation in view `body`
357
+ - [ ] Environment used for deeply shared state
358
+ - [ ] Custom `ViewModifier` for repeated styling
359
+ - [ ] `.sheet(item:)` preferred over `.sheet(isPresented:)`
360
+ - [ ] Sheets own their actions and call `dismiss()` internally
361
+ - [ ] MV pattern followed -- no unnecessary view models
362
+ - [ ] `@Observable` view model classes are `@MainActor`-isolated
363
+ - [ ] Model types passed across concurrency boundaries are `Sendable`
364
+
365
+ ## References
366
+
367
+ - Architecture, app wiring, and lightweight clients: [references/architecture-patterns.md](references/architecture-patterns.md)
368
+ - Design polish (HIG, theming, haptics, transitions, loading, focus): [references/design-polish.md](references/design-polish.md)
369
+ - Deprecated API migration: [references/deprecated-migration.md](references/deprecated-migration.md)
370
+ - Platform and sharing patterns (Transferable, media, menus, macOS settings): [references/platform-and-sharing.md](references/platform-and-sharing.md)
371
+