buildanything 1.7.1 → 1.8.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 (221) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +55 -0
  4. package/agents/ios-app-review-guardian.md +49 -0
  5. package/agents/ios-foundation-models-specialist.md +46 -0
  6. package/agents/ios-storekit-specialist.md +52 -0
  7. package/agents/ios-swift-architect.md +102 -0
  8. package/agents/ios-swift-search.md +130 -0
  9. package/agents/ios-swift-ui-design.md +104 -0
  10. package/commands/build.md +80 -176
  11. package/commands/fix.md +65 -0
  12. package/commands/setup.md +73 -0
  13. package/commands/ux-review.md +63 -0
  14. package/commands/verify.md +72 -0
  15. package/hooks/session-start +18 -1
  16. package/package.json +5 -2
  17. package/protocols/brainstorm.md +99 -0
  18. package/protocols/build-fix.md +52 -0
  19. package/protocols/cleanup.md +54 -0
  20. package/protocols/design.md +269 -0
  21. package/protocols/eval-harness.md +61 -0
  22. package/protocols/fake-data-detector.md +64 -0
  23. package/protocols/ios-context.md +235 -0
  24. package/protocols/ios-frameworks-map.md +323 -0
  25. package/protocols/ios-phase-branches.md +162 -0
  26. package/protocols/ios-preflight.md +27 -0
  27. package/protocols/metric-loop.md +93 -0
  28. package/protocols/planning.md +87 -0
  29. package/protocols/smoke-test.md +110 -0
  30. package/protocols/verify.md +67 -0
  31. package/protocols/web-phase-branches.md +201 -0
  32. package/skills/ios/_VENDORED.md +60 -0
  33. package/skills/ios/activitykit/LICENSE +131 -0
  34. package/skills/ios/activitykit/SKILL.md +505 -0
  35. package/skills/ios/activitykit/references/activitykit-patterns.md +868 -0
  36. package/skills/ios/app-intents/LICENSE +131 -0
  37. package/skills/ios/app-intents/SKILL.md +494 -0
  38. package/skills/ios/app-intents/references/appintents-advanced.md +1076 -0
  39. package/skills/ios/apple-on-device-ai/LICENSE +131 -0
  40. package/skills/ios/apple-on-device-ai/SKILL.md +505 -0
  41. package/skills/ios/apple-on-device-ai/references/coreml-conversion.md +425 -0
  42. package/skills/ios/apple-on-device-ai/references/coreml-optimization.md +344 -0
  43. package/skills/ios/apple-on-device-ai/references/foundation-models.md +508 -0
  44. package/skills/ios/apple-on-device-ai/references/mlx-swift.md +285 -0
  45. package/skills/ios/ios-26-platform/SKILL.md +53 -0
  46. package/skills/ios/ios-26-platform/references/automatic-adoption.md +161 -0
  47. package/skills/ios/ios-26-platform/references/backward-compat.md +238 -0
  48. package/skills/ios/ios-26-platform/references/liquid-glass.md +255 -0
  49. package/skills/ios/ios-26-platform/references/swiftui-apis.md +277 -0
  50. package/skills/ios/ios-26-platform/references/toolbar-navigation.md +250 -0
  51. package/skills/ios/ios-bootstrap/SKILL.md +98 -0
  52. package/skills/ios/ios-bootstrap/references/apple-docs-mcp-config.md +28 -0
  53. package/skills/ios/ios-bootstrap/references/new-project-dialog.md +41 -0
  54. package/skills/ios/ios-bootstrap/references/xcode-mcp-config.md +29 -0
  55. package/skills/ios/ios-debugger-agent/LICENSE +21 -0
  56. package/skills/ios/ios-debugger-agent/SKILL.md +58 -0
  57. package/skills/ios/ios-debugger-agent/agents/openai.yaml +4 -0
  58. package/skills/ios/ios-entitlements-generator/SKILL.md +47 -0
  59. package/skills/ios/ios-hig/SKILL.md +41 -0
  60. package/skills/ios/ios-hig/references/accessibility.md +81 -0
  61. package/skills/ios/ios-hig/references/content.md +142 -0
  62. package/skills/ios/ios-hig/references/feedback.md +123 -0
  63. package/skills/ios/ios-hig/references/interaction.md +199 -0
  64. package/skills/ios/ios-hig/references/performance-platform.md +129 -0
  65. package/skills/ios/ios-hig/references/privacy-permissions.md +181 -0
  66. package/skills/ios/ios-hig/references/visual-design.md +84 -0
  67. package/skills/ios/ios-info-plist-hardening/SKILL.md +130 -0
  68. package/skills/ios/ios-maestro-flow-author/SKILL.md +68 -0
  69. package/skills/ios/ios-maestro-flow-author/references/input-and-scroll.yaml +17 -0
  70. package/skills/ios/ios-maestro-flow-author/references/modal-and-dismiss.yaml +14 -0
  71. package/skills/ios/ios-maestro-flow-author/references/onboarding-flow.yaml +16 -0
  72. package/skills/ios/ios-maestro-flow-author/references/tab-navigation.yaml +13 -0
  73. package/skills/ios/ios-maestro-flow-author/references/tap-and-assert.yaml +9 -0
  74. package/skills/ios/swift-accessibility/LICENSE +21 -0
  75. package/skills/ios/swift-accessibility/SKILL.md +371 -0
  76. package/skills/ios/swift-accessibility/examples/before-after-appkit.md +446 -0
  77. package/skills/ios/swift-accessibility/examples/before-after-swiftui.md +441 -0
  78. package/skills/ios/swift-accessibility/examples/before-after-uikit.md +464 -0
  79. package/skills/ios/swift-accessibility/references/assistive-access.md +441 -0
  80. package/skills/ios/swift-accessibility/references/display-settings.md +491 -0
  81. package/skills/ios/swift-accessibility/references/dynamic-type.md +420 -0
  82. package/skills/ios/swift-accessibility/references/media-accessibility.md +421 -0
  83. package/skills/ios/swift-accessibility/references/motor-input.md +393 -0
  84. package/skills/ios/swift-accessibility/references/nutrition-labels.md +362 -0
  85. package/skills/ios/swift-accessibility/references/platform-specifics.md +515 -0
  86. package/skills/ios/swift-accessibility/references/semantic-structure.md +585 -0
  87. package/skills/ios/swift-accessibility/references/testing-auditing.md +507 -0
  88. package/skills/ios/swift-accessibility/references/voice-control.md +317 -0
  89. package/skills/ios/swift-accessibility/references/voiceover-swiftui.md +584 -0
  90. package/skills/ios/swift-accessibility/references/voiceover-uikit.md +519 -0
  91. package/skills/ios/swift-accessibility/references/wcag-mapping.md +167 -0
  92. package/skills/ios/swift-accessibility/resources/audit-template.swift +128 -0
  93. package/skills/ios/swift-accessibility/resources/qa-checklist.md +258 -0
  94. package/skills/ios/swift-concurrency/LICENSE +21 -0
  95. package/skills/ios/swift-concurrency/SKILL.md +171 -0
  96. package/skills/ios/swift-concurrency/references/_index.md +50 -0
  97. package/skills/ios/swift-concurrency/references/actors.md +660 -0
  98. package/skills/ios/swift-concurrency/references/async-algorithms.md +847 -0
  99. package/skills/ios/swift-concurrency/references/async-await-basics.md +266 -0
  100. package/skills/ios/swift-concurrency/references/async-sequences.md +710 -0
  101. package/skills/ios/swift-concurrency/references/core-data.md +560 -0
  102. package/skills/ios/swift-concurrency/references/glossary.md +135 -0
  103. package/skills/ios/swift-concurrency/references/linting.md +155 -0
  104. package/skills/ios/swift-concurrency/references/memory-management.md +569 -0
  105. package/skills/ios/swift-concurrency/references/migration.md +1104 -0
  106. package/skills/ios/swift-concurrency/references/performance.md +593 -0
  107. package/skills/ios/swift-concurrency/references/sendable.md +598 -0
  108. package/skills/ios/swift-concurrency/references/tasks.md +636 -0
  109. package/skills/ios/swift-concurrency/references/testing.md +592 -0
  110. package/skills/ios/swift-concurrency/references/threading.md +495 -0
  111. package/skills/ios/swift-security-expert/LICENSE +21 -0
  112. package/skills/ios/swift-security-expert/SKILL.md +470 -0
  113. package/skills/ios/swift-security-expert/references/biometric-authentication.md +565 -0
  114. package/skills/ios/swift-security-expert/references/certificate-trust.md +592 -0
  115. package/skills/ios/swift-security-expert/references/common-anti-patterns.md +690 -0
  116. package/skills/ios/swift-security-expert/references/compliance-owasp-mapping.md +537 -0
  117. package/skills/ios/swift-security-expert/references/credential-storage-patterns.md +721 -0
  118. package/skills/ios/swift-security-expert/references/cryptokit-public-key.md +505 -0
  119. package/skills/ios/swift-security-expert/references/cryptokit-symmetric.md +497 -0
  120. package/skills/ios/swift-security-expert/references/keychain-access-control.md +508 -0
  121. package/skills/ios/swift-security-expert/references/keychain-fundamentals.md +596 -0
  122. package/skills/ios/swift-security-expert/references/keychain-item-classes.md +476 -0
  123. package/skills/ios/swift-security-expert/references/keychain-sharing.md +458 -0
  124. package/skills/ios/swift-security-expert/references/migration-legacy-stores.md +727 -0
  125. package/skills/ios/swift-security-expert/references/secure-enclave.md +539 -0
  126. package/skills/ios/swift-security-expert/references/testing-security-code.md +781 -0
  127. package/skills/ios/swift-testing-expert/LICENSE +21 -0
  128. package/skills/ios/swift-testing-expert/SKILL.md +79 -0
  129. package/skills/ios/swift-testing-expert/references/_index.md +12 -0
  130. package/skills/ios/swift-testing-expert/references/async-testing-and-waiting.md +127 -0
  131. package/skills/ios/swift-testing-expert/references/expectations.md +145 -0
  132. package/skills/ios/swift-testing-expert/references/fundamentals.md +141 -0
  133. package/skills/ios/swift-testing-expert/references/migration-from-xctest.md +127 -0
  134. package/skills/ios/swift-testing-expert/references/parallelization-and-isolation.md +95 -0
  135. package/skills/ios/swift-testing-expert/references/parameterized-testing.md +284 -0
  136. package/skills/ios/swift-testing-expert/references/performance-and-best-practices.md +187 -0
  137. package/skills/ios/swift-testing-expert/references/traits-and-tags.md +114 -0
  138. package/skills/ios/swift-testing-expert/references/xcode-workflows.md +70 -0
  139. package/skills/ios/swiftdata-pro/LICENSE +21 -0
  140. package/skills/ios/swiftdata-pro/SKILL.md +102 -0
  141. package/skills/ios/swiftdata-pro/agents/openai.yaml +10 -0
  142. package/skills/ios/swiftdata-pro/assets/swiftdata-pro-icon.png +0 -0
  143. package/skills/ios/swiftdata-pro/assets/swiftdata-pro-icon.svg +29 -0
  144. package/skills/ios/swiftdata-pro/references/class-inheritance.md +104 -0
  145. package/skills/ios/swiftdata-pro/references/cloudkit.md +10 -0
  146. package/skills/ios/swiftdata-pro/references/core-rules.md +20 -0
  147. package/skills/ios/swiftdata-pro/references/indexing.md +27 -0
  148. package/skills/ios/swiftdata-pro/references/predicates.md +73 -0
  149. package/skills/ios/swiftui-design-principles/AGENTS.md +21 -0
  150. package/skills/ios/swiftui-design-principles/LICENSE +21 -0
  151. package/skills/ios/swiftui-design-principles/README.md +41 -0
  152. package/skills/ios/swiftui-design-principles/SKILL.md +605 -0
  153. package/skills/ios/swiftui-design-principles/metadata.json +10 -0
  154. package/skills/ios/swiftui-liquid-glass/LICENSE +21 -0
  155. package/skills/ios/swiftui-liquid-glass/SKILL.md +95 -0
  156. package/skills/ios/swiftui-liquid-glass/agents/openai.yaml +4 -0
  157. package/skills/ios/swiftui-liquid-glass/references/liquid-glass.md +280 -0
  158. package/skills/ios/swiftui-performance-audit/LICENSE +21 -0
  159. package/skills/ios/swiftui-performance-audit/SKILL.md +111 -0
  160. package/skills/ios/swiftui-performance-audit/agents/openai.yaml +4 -0
  161. package/skills/ios/swiftui-performance-audit/references/code-smells.md +150 -0
  162. package/skills/ios/swiftui-performance-audit/references/demystify-swiftui-performance-wwdc23.md +46 -0
  163. package/skills/ios/swiftui-performance-audit/references/optimizing-swiftui-performance-instruments.md +29 -0
  164. package/skills/ios/swiftui-performance-audit/references/profiling-intake.md +44 -0
  165. package/skills/ios/swiftui-performance-audit/references/report-template.md +47 -0
  166. package/skills/ios/swiftui-performance-audit/references/understanding-hangs-in-your-app.md +33 -0
  167. package/skills/ios/swiftui-performance-audit/references/understanding-improving-swiftui-performance.md +52 -0
  168. package/skills/ios/swiftui-pro/LICENSE +21 -0
  169. package/skills/ios/swiftui-pro/SKILL.md +108 -0
  170. package/skills/ios/swiftui-pro/agents/openai.yaml +10 -0
  171. package/skills/ios/swiftui-pro/assets/swiftui-pro-icon.png +0 -0
  172. package/skills/ios/swiftui-pro/assets/swiftui-pro-icon.svg +29 -0
  173. package/skills/ios/swiftui-pro/references/accessibility.md +13 -0
  174. package/skills/ios/swiftui-pro/references/api.md +39 -0
  175. package/skills/ios/swiftui-pro/references/data.md +43 -0
  176. package/skills/ios/swiftui-pro/references/design.md +31 -0
  177. package/skills/ios/swiftui-pro/references/hygiene.md +9 -0
  178. package/skills/ios/swiftui-pro/references/navigation.md +14 -0
  179. package/skills/ios/swiftui-pro/references/performance.md +46 -0
  180. package/skills/ios/swiftui-pro/references/swift.md +56 -0
  181. package/skills/ios/swiftui-pro/references/views.md +35 -0
  182. package/skills/ios/swiftui-ui-patterns/LICENSE +21 -0
  183. package/skills/ios/swiftui-ui-patterns/SKILL.md +100 -0
  184. package/skills/ios/swiftui-ui-patterns/agents/openai.yaml +4 -0
  185. package/skills/ios/swiftui-ui-patterns/references/app-wiring.md +201 -0
  186. package/skills/ios/swiftui-ui-patterns/references/async-state.md +96 -0
  187. package/skills/ios/swiftui-ui-patterns/references/components-index.md +50 -0
  188. package/skills/ios/swiftui-ui-patterns/references/controls.md +57 -0
  189. package/skills/ios/swiftui-ui-patterns/references/deeplinks.md +66 -0
  190. package/skills/ios/swiftui-ui-patterns/references/focus.md +90 -0
  191. package/skills/ios/swiftui-ui-patterns/references/form.md +97 -0
  192. package/skills/ios/swiftui-ui-patterns/references/grids.md +71 -0
  193. package/skills/ios/swiftui-ui-patterns/references/haptics.md +71 -0
  194. package/skills/ios/swiftui-ui-patterns/references/input-toolbar.md +51 -0
  195. package/skills/ios/swiftui-ui-patterns/references/lightweight-clients.md +93 -0
  196. package/skills/ios/swiftui-ui-patterns/references/list.md +86 -0
  197. package/skills/ios/swiftui-ui-patterns/references/loading-placeholders.md +38 -0
  198. package/skills/ios/swiftui-ui-patterns/references/macos-settings.md +71 -0
  199. package/skills/ios/swiftui-ui-patterns/references/matched-transitions.md +59 -0
  200. package/skills/ios/swiftui-ui-patterns/references/media.md +73 -0
  201. package/skills/ios/swiftui-ui-patterns/references/menu-bar.md +101 -0
  202. package/skills/ios/swiftui-ui-patterns/references/navigationstack.md +159 -0
  203. package/skills/ios/swiftui-ui-patterns/references/overlay.md +45 -0
  204. package/skills/ios/swiftui-ui-patterns/references/performance.md +62 -0
  205. package/skills/ios/swiftui-ui-patterns/references/previews.md +48 -0
  206. package/skills/ios/swiftui-ui-patterns/references/scroll-reveal.md +133 -0
  207. package/skills/ios/swiftui-ui-patterns/references/scrollview.md +87 -0
  208. package/skills/ios/swiftui-ui-patterns/references/searchable.md +71 -0
  209. package/skills/ios/swiftui-ui-patterns/references/sheets.md +155 -0
  210. package/skills/ios/swiftui-ui-patterns/references/split-views.md +72 -0
  211. package/skills/ios/swiftui-ui-patterns/references/tabview.md +114 -0
  212. package/skills/ios/swiftui-ui-patterns/references/theming.md +71 -0
  213. package/skills/ios/swiftui-ui-patterns/references/title-menus.md +93 -0
  214. package/skills/ios/swiftui-ui-patterns/references/top-bar.md +49 -0
  215. package/skills/ios/swiftui-view-refactor/LICENSE +21 -0
  216. package/skills/ios/swiftui-view-refactor/SKILL.md +207 -0
  217. package/skills/ios/swiftui-view-refactor/agents/openai.yaml +4 -0
  218. package/skills/ios/swiftui-view-refactor/references/mv-patterns.md +161 -0
  219. package/skills/ios/widgetkit/LICENSE +131 -0
  220. package/skills/ios/widgetkit/SKILL.md +502 -0
  221. package/skills/ios/widgetkit/references/widgetkit-advanced.md +871 -0
@@ -0,0 +1,56 @@
1
+ # Swift
2
+
3
+ - Prefer Swift-native string methods over Foundation equivalents: use `replacing("a", with: "b")` not `replacingOccurrences(of: "a", with: "b")`.
4
+ - Prefer modern Foundation API: `URL.documentsDirectory` instead of `FileManager` directory lookups, `appending(path:)` to append strings to a URL.
5
+ - Never use C-style number formatting like `String(format: "%.2f", value)`. Use `Text(value, format: .number.precision(.fractionLength(2)))` or similar `FormatStyle` APIs.
6
+ - Prefer static member lookup to struct instances where possible, such as `.circle` rather than `Circle()`, and `.borderedProminent` rather than `BorderedProminentButtonStyle()`.
7
+ - Avoid force unwraps (`!`) and force `try` unless the failure is truly unrecoverable, and even then prefer using `fatalError()` with a clear description. If possible, use `if let`, `guard let`, nil-coalescing, or `try?`/`do-catch`.
8
+ - Filtering text based on user-input must be done using `localizedStandardContains()` as opposed to `contains()` or `localizedCaseInsensitiveContains()`.
9
+ - Strongly prefer `Double` over `CGFloat`, except when using optionals or `inout`; Swift is able to bridge the two freely except in those two cases.
10
+ - If you want to count array objects that match a predicate, always use `count(where:)` rather than `filter()` followed by `count`.
11
+ - Prefer `Date.now` over `Date()` for clarity.
12
+ - When `import SwiftUI` is already in a file, you do not need to add `import UIKit` or `import AppKit` to access things like `UIImage` or `NSImage` – they are imported automatically on the appropriate platform.
13
+ - When dealing with the names of people, strongly prefer to use `PersonNameComponents` with modern formatting over simple string interpolation such as `Text("\(firstName) \(lastName)")`.
14
+ - If a given type of data is repeatedly sorted using an identical closure, e.g. `books.sorted { $0.author < $1.author }`, prefer to make the type in question conform to `Comparable` so the sort order is centralized.
15
+ - Prefer to avoid manual date formatting strings if possible. If manual date formatting *is* used for user display, at least make sure to use “y” rather than “yyyy” for years, so the year value is correct in all localizations. If the purpose is data exchange with an API, this rule does not apply.
16
+ - When trying to convert a string to a date, prefer the modern `Date` initializer API such as `Date(myString, strategy: .iso8601)`.
17
+ - Flag instances where errors triggered by a user action are swallowed silently, e.g. using `print(error.localizedDescription)` rather than showing an alert or similar.
18
+ - Prefer `if let value {` shorthand over `if let value = value {`.
19
+ - Omit return for single expression functions. `if` and `switch` can be used as expressions when returning values and assigning to variables.
20
+
21
+ For example, this kind of code:
22
+
23
+ ```swift
24
+ var tileColor: Color {
25
+ if isCorrect {
26
+ return .green
27
+ } else {
28
+ return .red
29
+ }
30
+ }
31
+ ```
32
+
33
+ Should be written like this:
34
+
35
+ ```swift
36
+ var tileColor: Color {
37
+ if isCorrect {
38
+ .green
39
+ } else {
40
+ .red
41
+ }
42
+ }
43
+ ```
44
+
45
+
46
+ ## Swift Concurrency
47
+
48
+ - If an API offers both modern `async`/`await` equivalents and older closure-based variants, always prefer the `async`/`await` versions.
49
+ - Never use Grand Central Dispatch (`DispatchQueue.main.async()`, `DispatchQueue.global()`, etc.). Always use modern Swift concurrency (`async`/`await`, actors, `Task`).
50
+ - Never use `Task.sleep(nanoseconds:)`; use `Task.sleep(for:)` instead.
51
+ - Flag any mutable shared state that isn't protected by an actor or `@MainActor`, unless the project is configured to use MainActor default actor isolation.
52
+ - Assume strict concurrency rules are being applied; flag `@Sendable` violations and data races.
53
+ - When evaluating `MainActor.run()`, check whether the project has its default actor isolation set to Main Actor first, because `MainActor.run()` might not be needed.
54
+ - `Task.detached()` is often a bad idea. Check any usage extremely carefully.
55
+
56
+ For more help with Swift concurrency, suggest the [Swift Concurrency Pro agent skill](https://github.com/twostraws/swift-concurrency-agent-skill).
@@ -0,0 +1,35 @@
1
+ # SwiftUI Views
2
+
3
+ - Strongly prefer to avoid breaking up view bodies using computed properties or methods that return `some View`, even if `@ViewBuilder` is used. Extract them into separate `View` structs instead, placing each into its own file.
4
+ - Flag `body` properties that are excessively long; they should be broken into extracted subviews.
5
+ - Button actions should be extracted from view bodies into separate methods, to avoid mixing layout and logic.
6
+ - Similarly, general business logic should not live inline in `task()`, `onAppear()` or elsewhere in `body`.
7
+ - Prefer to place view logic into view models or similar, so it can be tested. For more help with testing, suggest the [Swift Testing Pro agent skill](https://github.com/twostraws/swift-testing-agent-skill).
8
+ - Each type (struct, class, enum) should be in its own Swift file. Flag files containing multiple type definitions.
9
+ - Unless a full-screen editing experience is required, prefer using `TextField` with `axis: .vertical` to using `TextEditor`, because it allows placeholder text. If a specific minimum height is required for `TextField`, use something like `lineLimit(5...)`.
10
+ - If a button action can be provided directly as an `action` parameter, do so. For example: `Button("Label", systemImage: "plus", action: myAction)` is preferred over `Button("Label", systemImage: "plus") { action() }`.
11
+ - When rendering SwiftUI views to images, strongly prefer `ImageRenderer` over `UIGraphicsImageRenderer`.
12
+ - `#Preview` should be used for previews, not the legacy `PreviewProvider` protocol.
13
+ - When using `TabView(selection:)`, use a binding to a property that stores an enum rather than an integer or string. For example, `Tab("Home", systemImage: "house", value: .home)` is better than `Tab("Home", systemImage: "house", value: 0)`.
14
+ - Strongly prefer to avoid breaking up view bodies using computed properties or methods that return `some View`, even if `@ViewBuilder` is used. Extract them into separate `View` structs instead, placing each into its own file. (Yes, this is repeated, but it’s so important it needs to be mentioned twice.)
15
+
16
+
17
+ ## Animating views
18
+
19
+ - Strongly prefer to use the `@Animatable` macro over creating `animatableData` manually – the macro automatically adds conformance to the `Animatable` protocol and creates the correct `animatableData` property. If some properties should not or cannot be animated (e.g. Booleans, integers, etc), mark them `@AnimatableIgnored`.
20
+ - Never use `animation(_ animation: Animation?)`; always provide a value to watch, such as `.animation(.bouncy, value: score)`.
21
+ - Chaining animations must be done using a `completion` closure passed to `withAnimation()`, rather than trying to execute multiple `withAnimation()` calls using delays.
22
+
23
+ For example:
24
+
25
+ ```swift
26
+ Button("Animate Me") {
27
+ withAnimation {
28
+ scale = 2
29
+ } completion: {
30
+ withAnimation {
31
+ scale = 1
32
+ }
33
+ }
34
+ }
35
+ ```
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Thomas Ricouard
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,100 @@
1
+ ---
2
+ name: swiftui-ui-patterns
3
+ description: Fires ONLY on these specific composition tasks — creating a new TabView, composing a NavigationStack hierarchy, authoring a Sheet presentation (enum-driven sheets, `.sheet(item:)`), implementing scroll-reveal gestures, or authoring custom ViewModifiers. Does NOT overlap swiftui-pro — swiftui-pro handles general SwiftUI review and write operations; this skill is scoped only to the composition tasks listed above. Do not load on general SwiftUI writes.
4
+ ---
5
+
6
+ # SwiftUI UI Patterns
7
+
8
+ ## Quick start
9
+
10
+ Choose a track based on your goal:
11
+
12
+ ### Existing project
13
+
14
+ - Identify the feature or screen and the primary interaction model (list, detail, editor, settings, tabbed).
15
+ - Find a nearby example in the repo with `rg "TabView\("` or similar, then read the closest SwiftUI view.
16
+ - Apply local conventions: prefer SwiftUI-native state, keep state local when possible, and use environment injection for shared dependencies.
17
+ - Choose the relevant component reference from `references/components-index.md` and follow its guidance.
18
+ - If the interaction reveals secondary content by dragging or scrolling the primary content away, read `references/scroll-reveal.md` before implementing gestures manually.
19
+ - Build the view with small, focused subviews and SwiftUI-native data flow.
20
+
21
+ ### New project scaffolding
22
+
23
+ - Start with `references/app-wiring.md` to wire TabView + NavigationStack + sheets.
24
+ - Add a minimal `AppTab` and `RouterPath` based on the provided skeletons.
25
+ - Choose the next component reference based on the UI you need first (TabView, NavigationStack, Sheets).
26
+ - Expand the route and sheet enums as new screens are added.
27
+
28
+ ## General rules to follow
29
+
30
+ - Use modern SwiftUI state (`@State`, `@Binding`, `@Observable`, `@Environment`) and avoid unnecessary view models.
31
+ - If the deployment target includes iOS 16 or earlier and cannot use the Observation API introduced in iOS 17, fall back to `ObservableObject` with `@StateObject` for root ownership, `@ObservedObject` for injected observation, and `@EnvironmentObject` only for truly shared app-level state.
32
+ - Prefer composition; keep views small and focused.
33
+ - Use async/await with `.task` and explicit loading/error states. For restart, cancellation, and debouncing guidance, read `references/async-state.md`.
34
+ - Keep shared app services in `@Environment`, but prefer explicit initializer injection for feature-local dependencies and models. For root wiring patterns, read `references/app-wiring.md`.
35
+ - Prefer the newest SwiftUI API that fits the deployment target and call out the minimum OS whenever a pattern depends on it.
36
+ - Maintain existing legacy patterns only when editing legacy files.
37
+ - Follow the project's formatter and style guide.
38
+ - **Sheets**: Prefer `.sheet(item:)` over `.sheet(isPresented:)` when state represents a selected model. Avoid `if let` inside a sheet body. Sheets should own their actions and call `dismiss()` internally instead of forwarding `onCancel`/`onConfirm` closures.
39
+ - **Scroll-driven reveals**: Prefer deriving a normalized progress value from scroll offset and driving the visual state from that single source of truth. Avoid parallel gesture state machines unless scroll alone cannot express the interaction.
40
+
41
+ ## State ownership summary
42
+
43
+ Use the narrowest state tool that matches the ownership model:
44
+
45
+ | Scenario | Preferred pattern |
46
+ | --- | --- |
47
+ | Local UI state owned by one view | `@State` |
48
+ | Child mutates parent-owned value state | `@Binding` |
49
+ | Root-owned reference model on iOS 17+ | `@State` with an `@Observable` type |
50
+ | Child reads or mutates an injected `@Observable` model on iOS 17+ | Pass it explicitly as a stored property |
51
+ | Shared app service or configuration | `@Environment(Type.self)` |
52
+ | Legacy reference model on iOS 16 and earlier | `@StateObject` at the root, `@ObservedObject` when injected |
53
+
54
+ Choose the ownership location first, then pick the wrapper. Do not introduce a reference model when plain value state is enough.
55
+
56
+ ## Cross-cutting references
57
+
58
+ - `references/navigationstack.md`: navigation ownership, per-tab history, and enum routing.
59
+ - `references/sheets.md`: centralized modal presentation and enum-driven sheets.
60
+ - `references/deeplinks.md`: URL handling and routing external links into app destinations.
61
+ - `references/app-wiring.md`: root dependency graph, environment usage, and app shell wiring.
62
+ - `references/async-state.md`: `.task`, `.task(id:)`, cancellation, debouncing, and async UI state.
63
+ - `references/previews.md`: `#Preview`, fixtures, mock environments, and isolated preview setup.
64
+ - `references/performance.md`: stable identity, observation scope, lazy containers, and render-cost guardrails.
65
+
66
+ ## Anti-patterns
67
+
68
+ - Giant views that mix layout, business logic, networking, routing, and formatting in one file.
69
+ - Multiple boolean flags for mutually exclusive sheets, alerts, or navigation destinations.
70
+ - Live service calls directly inside `body`-driven code paths instead of view lifecycle hooks or injected models/services.
71
+ - Reaching for `AnyView` to work around type mismatches that should be solved with better composition.
72
+ - Defaulting every shared dependency to `@EnvironmentObject` or a global router without a clear ownership reason.
73
+
74
+ ## Workflow for a new SwiftUI view
75
+
76
+ 1. Define the view's state, ownership location, and minimum OS assumptions before writing UI code.
77
+ 2. Identify which dependencies belong in `@Environment` and which should stay as explicit initializer inputs.
78
+ 3. Sketch the view hierarchy, routing model, and presentation points; extract repeated parts into subviews. For complex navigation, read `references/navigationstack.md`, `references/sheets.md`, or `references/deeplinks.md`. **Build and verify no compiler errors before proceeding.**
79
+ 4. Implement async loading with `.task` or `.task(id:)`, plus explicit loading and error states when needed. Read `references/async-state.md` when the work depends on changing inputs or cancellation.
80
+ 5. Add previews for the primary and secondary states, then add accessibility labels or identifiers when the UI is interactive. Read `references/previews.md` when the view needs fixtures or injected mock dependencies.
81
+ 6. Validate with a build: confirm no compiler errors, check that previews render without crashing, ensure state changes propagate correctly, and sanity-check that list identity and observation scope will not cause avoidable re-renders. Read `references/performance.md` if the screen is large, scroll-heavy, or frequently updated. For common SwiftUI compilation errors — missing `@State` annotations, ambiguous `ViewBuilder` closures, or mismatched generic types — resolve them before updating callsites. **If the build fails:** read the error message carefully, fix the identified issue, then rebuild before proceeding to the next step. If a preview crashes, isolate the offending subview, confirm its state initialisation is valid, and re-run the preview before continuing.
82
+
83
+ ## Component references
84
+
85
+ Use `references/components-index.md` as the entry point. Each component reference should include:
86
+ - Intent and best-fit scenarios.
87
+ - Minimal usage pattern with local conventions.
88
+ - Pitfalls and performance notes.
89
+ - Paths to existing examples in the current repo.
90
+
91
+ ## Adding a new component reference
92
+
93
+ - Create `references/<component>.md`.
94
+ - Keep it short and actionable; link to concrete files in the current repo.
95
+ - Update `references/components-index.md` with the new entry.
96
+
97
+
98
+ ---
99
+
100
+ Vendored from: https://github.com/Dimillian/Skills/tree/main/swiftui-ui-patterns
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "SwiftUI UI Patterns"
3
+ short_description: "Apply practical SwiftUI UI patterns"
4
+ default_prompt: "Use $swiftui-ui-patterns to design or refactor this SwiftUI UI with strong default patterns."
@@ -0,0 +1,201 @@
1
+ # App wiring and dependency graph
2
+
3
+ ## Intent
4
+
5
+ Show how to wire the app shell (TabView + NavigationStack + sheets) and install a global dependency graph (environment objects, services, streaming clients, SwiftData ModelContainer) in one place.
6
+
7
+ ## Recommended structure
8
+
9
+ 1) Root view sets up tabs, per-tab routers, and sheets.
10
+ 2) A dedicated view modifier installs global dependencies and lifecycle tasks (auth state, streaming watchers, push tokens, data containers).
11
+ 3) Feature views pull only what they need from the environment; feature-specific state stays local.
12
+
13
+ ## Dependency selection
14
+
15
+ - Use `@Environment` for app-level services, shared clients, theme/configuration, and values that many descendants genuinely need.
16
+ - Prefer initializer injection for feature-local dependencies and models. Do not move a dependency into the environment just to avoid passing one or two arguments.
17
+ - Keep mutable feature state out of the environment unless it is intentionally shared across broad parts of the app.
18
+ - Use `@EnvironmentObject` only as a legacy fallback or when the project already standardizes on it for a truly shared object.
19
+
20
+ ## Root shell example (generic)
21
+
22
+ ```swift
23
+ @MainActor
24
+ struct AppView: View {
25
+ @State private var selectedTab: AppTab = .home
26
+ @State private var tabRouter = TabRouter()
27
+
28
+ var body: some View {
29
+ TabView(selection: $selectedTab) {
30
+ ForEach(AppTab.allCases) { tab in
31
+ let router = tabRouter.router(for: tab)
32
+ NavigationStack(path: tabRouter.binding(for: tab)) {
33
+ tab.makeContentView()
34
+ }
35
+ .withSheetDestinations(sheet: Binding(
36
+ get: { router.presentedSheet },
37
+ set: { router.presentedSheet = $0 }
38
+ ))
39
+ .environment(router)
40
+ .tabItem { tab.label }
41
+ .tag(tab)
42
+ }
43
+ }
44
+ .withAppDependencyGraph()
45
+ }
46
+ }
47
+ ```
48
+
49
+ Minimal `AppTab` example:
50
+
51
+ ```swift
52
+ @MainActor
53
+ enum AppTab: Identifiable, Hashable, CaseIterable {
54
+ case home, notifications, settings
55
+ var id: String { String(describing: self) }
56
+
57
+ @ViewBuilder
58
+ func makeContentView() -> some View {
59
+ switch self {
60
+ case .home: HomeView()
61
+ case .notifications: NotificationsView()
62
+ case .settings: SettingsView()
63
+ }
64
+ }
65
+
66
+ @ViewBuilder
67
+ var label: some View {
68
+ switch self {
69
+ case .home: Label("Home", systemImage: "house")
70
+ case .notifications: Label("Notifications", systemImage: "bell")
71
+ case .settings: Label("Settings", systemImage: "gear")
72
+ }
73
+ }
74
+ }
75
+ ```
76
+
77
+ Router skeleton:
78
+
79
+ ```swift
80
+ @MainActor
81
+ @Observable
82
+ final class RouterPath {
83
+ var path: [Route] = []
84
+ var presentedSheet: SheetDestination?
85
+ }
86
+
87
+ enum Route: Hashable {
88
+ case detail(id: String)
89
+ }
90
+ ```
91
+
92
+ ## Dependency graph modifier (generic)
93
+
94
+ Use a single modifier to install environment objects and handle lifecycle hooks when the active account/client changes. This keeps wiring consistent and avoids forgetting a dependency in call sites.
95
+
96
+ ```swift
97
+ extension View {
98
+ func withAppDependencyGraph(
99
+ accountManager: AccountManager = .shared,
100
+ currentAccount: CurrentAccount = .shared,
101
+ currentInstance: CurrentInstance = .shared,
102
+ userPreferences: UserPreferences = .shared,
103
+ theme: Theme = .shared,
104
+ watcher: StreamWatcher = .shared,
105
+ pushNotifications: PushNotificationsService = .shared,
106
+ intentService: AppIntentService = .shared,
107
+ quickLook: QuickLook = .shared,
108
+ toastCenter: ToastCenter = .shared,
109
+ namespace: Namespace.ID? = nil,
110
+ isSupporter: Bool = false
111
+ ) -> some View {
112
+ environment(accountManager)
113
+ .environment(accountManager.currentClient)
114
+ .environment(quickLook)
115
+ .environment(currentAccount)
116
+ .environment(currentInstance)
117
+ .environment(userPreferences)
118
+ .environment(theme)
119
+ .environment(watcher)
120
+ .environment(pushNotifications)
121
+ .environment(intentService)
122
+ .environment(toastCenter)
123
+ .environment(\.isSupporter, isSupporter)
124
+ .task(id: accountManager.currentClient.id) {
125
+ let client = accountManager.currentClient
126
+ if let namespace { quickLook.namespace = namespace }
127
+ currentAccount.setClient(client: client)
128
+ currentInstance.setClient(client: client)
129
+ userPreferences.setClient(client: client)
130
+ await currentInstance.fetchCurrentInstance()
131
+ watcher.setClient(client: client, instanceStreamingURL: currentInstance.instance?.streamingURL)
132
+ if client.isAuth {
133
+ watcher.watch(streams: [.user, .direct])
134
+ } else {
135
+ watcher.stopWatching()
136
+ }
137
+ }
138
+ .task(id: accountManager.pushAccounts.map(\.token)) {
139
+ pushNotifications.tokens = accountManager.pushAccounts.map(\.token)
140
+ }
141
+ }
142
+ }
143
+ ```
144
+
145
+ Notes:
146
+ - The `.task(id:)` hooks respond to account/client changes, re-seeding services and watcher state.
147
+ - Keep the modifier focused on global wiring; feature-specific state stays within features.
148
+ - Adjust types (AccountManager, StreamWatcher, etc.) to match your project.
149
+
150
+ ## SwiftData / ModelContainer
151
+
152
+ Install your `ModelContainer` at the root so all feature views share the same store. Keep the list minimal to the models that need persistence.
153
+
154
+ ```swift
155
+ extension View {
156
+ func withModelContainer() -> some View {
157
+ modelContainer(for: [Draft.self, LocalTimeline.self, TagGroup.self])
158
+ }
159
+ }
160
+ ```
161
+
162
+ Why: a single container avoids duplicated stores per sheet or tab and keeps data consistent.
163
+
164
+ ## Sheet routing (enum-driven)
165
+
166
+ Centralize sheets with a small enum and a helper modifier.
167
+
168
+ ```swift
169
+ enum SheetDestination: Identifiable {
170
+ case composer
171
+ case settings
172
+ var id: String { String(describing: self) }
173
+ }
174
+
175
+ extension View {
176
+ func withSheetDestinations(sheet: Binding<SheetDestination?>) -> some View {
177
+ sheet(item: sheet) { destination in
178
+ switch destination {
179
+ case .composer:
180
+ ComposerView().withEnvironments()
181
+ case .settings:
182
+ SettingsView().withEnvironments()
183
+ }
184
+ }
185
+ }
186
+ }
187
+ ```
188
+
189
+ Why: enum-driven sheets keep presentation centralized and testable; adding a new sheet means adding one enum case and one switch branch.
190
+
191
+ ## When to use
192
+
193
+ - Apps with multiple packages/modules that share environment objects and services.
194
+ - Apps that need to react to account/client changes and rewire streaming/push safely.
195
+ - Any app that wants consistent TabView + NavigationStack + sheet wiring without repeating environment setup.
196
+
197
+ ## Caveats
198
+
199
+ - Keep the dependency modifier slim; do not put feature state or heavy logic there.
200
+ - Ensure `.task(id:)` work is lightweight or cancelled appropriately; long-running work belongs in services.
201
+ - If unauthenticated clients exist, gate streaming/watch calls to avoid reconnect spam.
@@ -0,0 +1,96 @@
1
+ # Async state and task lifecycle
2
+
3
+ ## Intent
4
+
5
+ Use this pattern when a view loads data, reacts to changing input, or coordinates async work that should follow the SwiftUI view lifecycle.
6
+
7
+ ## Core rules
8
+
9
+ - Use `.task` for load-on-appear work that belongs to the view lifecycle.
10
+ - Use `.task(id:)` when async work should restart for a changing input such as a query, selection, or identifier.
11
+ - Treat cancellation as a normal path for view-driven tasks. Check `Task.isCancelled` in longer flows and avoid surfacing cancellation as a user-facing error.
12
+ - Debounce or coalesce user-driven async work such as search before it fans out into repeated requests.
13
+ - Keep UI-facing models and mutations main-actor-safe; do background work in services, then publish the result back to UI state.
14
+
15
+ ## Example: load on appear
16
+
17
+ ```swift
18
+ struct DetailView: View {
19
+ let id: String
20
+ @State private var state: LoadState<Item> = .idle
21
+ @Environment(ItemClient.self) private var client
22
+
23
+ var body: some View {
24
+ content
25
+ .task {
26
+ await load()
27
+ }
28
+ }
29
+
30
+ @ViewBuilder
31
+ private var content: some View {
32
+ switch state {
33
+ case .idle, .loading:
34
+ ProgressView()
35
+ case .loaded(let item):
36
+ ItemContent(item: item)
37
+ case .failed(let error):
38
+ ErrorView(error: error)
39
+ }
40
+ }
41
+
42
+ private func load() async {
43
+ state = .loading
44
+ do {
45
+ state = .loaded(try await client.fetch(id: id))
46
+ } catch is CancellationError {
47
+ return
48
+ } catch {
49
+ state = .failed(error)
50
+ }
51
+ }
52
+ }
53
+ ```
54
+
55
+ ## Example: restart on input change
56
+
57
+ ```swift
58
+ struct SearchView: View {
59
+ @State private var query = ""
60
+ @State private var results: [ResultItem] = []
61
+ @Environment(SearchClient.self) private var client
62
+
63
+ var body: some View {
64
+ List(results) { item in
65
+ Text(item.title)
66
+ }
67
+ .searchable(text: $query)
68
+ .task(id: query) {
69
+ try? await Task.sleep(for: .milliseconds(250))
70
+ guard !Task.isCancelled, !query.isEmpty else {
71
+ results = []
72
+ return
73
+ }
74
+ do {
75
+ results = try await client.search(query)
76
+ } catch is CancellationError {
77
+ return
78
+ } catch {
79
+ results = []
80
+ }
81
+ }
82
+ }
83
+ }
84
+ ```
85
+
86
+ ## When to move work out of the view
87
+
88
+ - If the async flow spans multiple screens or must survive view dismissal, move it into a service or model.
89
+ - If the view is mostly coordinating app-level lifecycle or account changes, wire it at the app shell in `app-wiring.md`.
90
+ - If retry, caching, or offline policy becomes complex, keep the policy in the client/service and leave the view with simple state transitions.
91
+
92
+ ## Pitfalls
93
+
94
+ - Do not start network work directly from `body`.
95
+ - Do not ignore cancellation for searches, typeahead, or rapidly changing selections.
96
+ - Avoid storing derived async state in multiple places when one source of truth is enough.
@@ -0,0 +1,50 @@
1
+ # Components Index
2
+
3
+ Use this file to find component and cross-cutting guidance. Each entry lists when to use it.
4
+
5
+ ## Available components
6
+
7
+ - TabView: `references/tabview.md` — Use when building a tab-based app or any tabbed feature set.
8
+ - NavigationStack: `references/navigationstack.md` — Use when you need push navigation and programmatic routing, especially per-tab history.
9
+ - Sheets and presentation: `references/sheets.md` — Use for local item-driven sheets, centralized modal routing, and sheet-specific action patterns.
10
+ - Form and Settings: `references/form.md` — Use for settings, grouped inputs, and structured data entry.
11
+ - macOS Settings: `references/macos-settings.md` — Use when building a macOS Settings window with SwiftUI's Settings scene.
12
+ - Split views and columns: `references/split-views.md` — Use for iPad/macOS multi-column layouts or custom secondary columns.
13
+ - List and Section: `references/list.md` — Use for feed-style content and settings rows.
14
+ - ScrollView and Lazy stacks: `references/scrollview.md` — Use for custom layouts, horizontal scrollers, or grids.
15
+ - Scroll-reveal detail surfaces: `references/scroll-reveal.md` — Use when a detail screen reveals secondary content or actions as the user scrolls or swipes between full-screen sections.
16
+ - Grids: `references/grids.md` — Use for icon pickers, media galleries, and tiled layouts.
17
+ - Theming and dynamic type: `references/theming.md` — Use for app-wide theme tokens, colors, and type scaling.
18
+ - Controls (toggles, pickers, sliders): `references/controls.md` — Use for settings controls and input selection.
19
+ - Input toolbar (bottom anchored): `references/input-toolbar.md` — Use for chat/composer screens with a sticky input bar.
20
+ - Top bar overlays (iOS 26+ and fallback): `references/top-bar.md` — Use for pinned selectors or pills above scroll content.
21
+ - Overlay and toasts: `references/overlay.md` — Use for transient UI like banners or toasts.
22
+ - Focus handling: `references/focus.md` — Use for chaining fields and keyboard focus management.
23
+ - Searchable: `references/searchable.md` — Use for native search UI with scopes and async results.
24
+ - Async images and media: `references/media.md` — Use for remote media, previews, and media viewers.
25
+ - Haptics: `references/haptics.md` — Use for tactile feedback tied to key actions.
26
+ - Matched transitions: `references/matched-transitions.md` — Use for smooth source-to-destination animations.
27
+ - Deep links and URL routing: `references/deeplinks.md` — Use for in-app navigation from URLs.
28
+ - Title menus: `references/title-menus.md` — Use for filter or context menus in the navigation title.
29
+ - Menu bar commands: `references/menu-bar.md` — Use when adding or customizing macOS/iPadOS menu bar commands.
30
+ - Loading & placeholders: `references/loading-placeholders.md` — Use for redacted skeletons, empty states, and loading UX.
31
+ - Lightweight clients: `references/lightweight-clients.md` — Use for small, closure-based API clients injected into stores.
32
+
33
+ ## Cross-cutting references
34
+
35
+ - App wiring and dependency graph: `references/app-wiring.md` — Use to wire the app shell, install shared dependencies, and decide what belongs in the environment.
36
+ - Async state and task lifecycle: `references/async-state.md` — Use when a view loads data, reacts to changing input, or needs cancellation/debouncing guidance.
37
+ - Previews: `references/previews.md` — Use when adding `#Preview`, fixtures, mock environments, or isolated preview setup.
38
+ - Performance guardrails: `references/performance.md` — Use when a screen is large, scroll-heavy, frequently updated, or showing signs of avoidable re-renders.
39
+
40
+ ## Planned components (create files as needed)
41
+
42
+ - Web content: create `references/webview.md` — Use for embedded web content or in-app browsing.
43
+ - Status composer patterns: create `references/composer.md` — Use for composition or editor workflows.
44
+ - Text input and validation: create `references/text-input.md` — Use for forms, validation, and text-heavy input.
45
+ - Design system usage: create `references/design-system.md` — Use when applying shared styling rules.
46
+
47
+ ## Adding entries
48
+
49
+ - Add the component file and link it here with a short “when to use” description.
50
+ - Keep each component reference short and actionable.
@@ -0,0 +1,57 @@
1
+ # Controls (Toggle, Slider, Picker)
2
+
3
+ ## Intent
4
+
5
+ Use native controls for settings and configuration screens, keeping labels accessible and state bindings clear.
6
+
7
+ ## Core patterns
8
+
9
+ - Bind controls directly to `@State`, `@Binding`, or `@AppStorage`.
10
+ - Prefer `Toggle` for boolean preferences.
11
+ - Use `Slider` for numeric ranges and show the current value in a label.
12
+ - Use `Picker` for discrete choices; use `.pickerStyle(.segmented)` only for 2–4 options.
13
+ - Keep labels visible and descriptive; avoid embedding buttons inside controls.
14
+
15
+ ## Example: toggles with sections
16
+
17
+ ```swift
18
+ Form {
19
+ Section("Notifications") {
20
+ Toggle("Mentions", isOn: $preferences.notificationsMentionsEnabled)
21
+ Toggle("Follows", isOn: $preferences.notificationsFollowsEnabled)
22
+ Toggle("Boosts", isOn: $preferences.notificationsBoostsEnabled)
23
+ }
24
+ }
25
+ ```
26
+
27
+ ## Example: slider with value text
28
+
29
+ ```swift
30
+ Section("Font Size") {
31
+ Slider(value: $fontSizeScale, in: 0.5...1.5, step: 0.1)
32
+ Text("Scale: \(String(format: \"%.1f\", fontSizeScale))")
33
+ .font(.scaledBody)
34
+ }
35
+ ```
36
+
37
+ ## Example: picker for enums
38
+
39
+ ```swift
40
+ Picker("Default Visibility", selection: $visibility) {
41
+ ForEach(Visibility.allCases, id: \.self) { option in
42
+ Text(option.title).tag(option)
43
+ }
44
+ }
45
+ ```
46
+
47
+ ## Design choices to keep
48
+
49
+ - Group related controls in a `Form` section.
50
+ - Use `.disabled(...)` to reflect locked or inherited settings.
51
+ - Use `Label` inside toggles to combine icon + text when it adds clarity.
52
+
53
+ ## Pitfalls
54
+
55
+ - Avoid `.pickerStyle(.segmented)` for large sets; use menu or inline styles instead.
56
+ - Don’t hide labels for sliders; always show context.
57
+ - Avoid hard-coding colors for controls; use theme tint sparingly.