buildanything 1.7.0 → 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 (222) 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/README.md +71 -61
  5. package/agents/ios-app-review-guardian.md +49 -0
  6. package/agents/ios-foundation-models-specialist.md +46 -0
  7. package/agents/ios-storekit-specialist.md +52 -0
  8. package/agents/ios-swift-architect.md +102 -0
  9. package/agents/ios-swift-search.md +130 -0
  10. package/agents/ios-swift-ui-design.md +104 -0
  11. package/commands/build.md +80 -176
  12. package/commands/fix.md +65 -0
  13. package/commands/setup.md +73 -0
  14. package/commands/ux-review.md +63 -0
  15. package/commands/verify.md +72 -0
  16. package/hooks/session-start +18 -1
  17. package/package.json +5 -2
  18. package/protocols/brainstorm.md +99 -0
  19. package/protocols/build-fix.md +52 -0
  20. package/protocols/cleanup.md +54 -0
  21. package/protocols/design.md +269 -0
  22. package/protocols/eval-harness.md +61 -0
  23. package/protocols/fake-data-detector.md +64 -0
  24. package/protocols/ios-context.md +235 -0
  25. package/protocols/ios-frameworks-map.md +323 -0
  26. package/protocols/ios-phase-branches.md +162 -0
  27. package/protocols/ios-preflight.md +27 -0
  28. package/protocols/metric-loop.md +93 -0
  29. package/protocols/planning.md +87 -0
  30. package/protocols/smoke-test.md +110 -0
  31. package/protocols/verify.md +67 -0
  32. package/protocols/web-phase-branches.md +201 -0
  33. package/skills/ios/_VENDORED.md +60 -0
  34. package/skills/ios/activitykit/LICENSE +131 -0
  35. package/skills/ios/activitykit/SKILL.md +505 -0
  36. package/skills/ios/activitykit/references/activitykit-patterns.md +868 -0
  37. package/skills/ios/app-intents/LICENSE +131 -0
  38. package/skills/ios/app-intents/SKILL.md +494 -0
  39. package/skills/ios/app-intents/references/appintents-advanced.md +1076 -0
  40. package/skills/ios/apple-on-device-ai/LICENSE +131 -0
  41. package/skills/ios/apple-on-device-ai/SKILL.md +505 -0
  42. package/skills/ios/apple-on-device-ai/references/coreml-conversion.md +425 -0
  43. package/skills/ios/apple-on-device-ai/references/coreml-optimization.md +344 -0
  44. package/skills/ios/apple-on-device-ai/references/foundation-models.md +508 -0
  45. package/skills/ios/apple-on-device-ai/references/mlx-swift.md +285 -0
  46. package/skills/ios/ios-26-platform/SKILL.md +53 -0
  47. package/skills/ios/ios-26-platform/references/automatic-adoption.md +161 -0
  48. package/skills/ios/ios-26-platform/references/backward-compat.md +238 -0
  49. package/skills/ios/ios-26-platform/references/liquid-glass.md +255 -0
  50. package/skills/ios/ios-26-platform/references/swiftui-apis.md +277 -0
  51. package/skills/ios/ios-26-platform/references/toolbar-navigation.md +250 -0
  52. package/skills/ios/ios-bootstrap/SKILL.md +98 -0
  53. package/skills/ios/ios-bootstrap/references/apple-docs-mcp-config.md +28 -0
  54. package/skills/ios/ios-bootstrap/references/new-project-dialog.md +41 -0
  55. package/skills/ios/ios-bootstrap/references/xcode-mcp-config.md +29 -0
  56. package/skills/ios/ios-debugger-agent/LICENSE +21 -0
  57. package/skills/ios/ios-debugger-agent/SKILL.md +58 -0
  58. package/skills/ios/ios-debugger-agent/agents/openai.yaml +4 -0
  59. package/skills/ios/ios-entitlements-generator/SKILL.md +47 -0
  60. package/skills/ios/ios-hig/SKILL.md +41 -0
  61. package/skills/ios/ios-hig/references/accessibility.md +81 -0
  62. package/skills/ios/ios-hig/references/content.md +142 -0
  63. package/skills/ios/ios-hig/references/feedback.md +123 -0
  64. package/skills/ios/ios-hig/references/interaction.md +199 -0
  65. package/skills/ios/ios-hig/references/performance-platform.md +129 -0
  66. package/skills/ios/ios-hig/references/privacy-permissions.md +181 -0
  67. package/skills/ios/ios-hig/references/visual-design.md +84 -0
  68. package/skills/ios/ios-info-plist-hardening/SKILL.md +130 -0
  69. package/skills/ios/ios-maestro-flow-author/SKILL.md +68 -0
  70. package/skills/ios/ios-maestro-flow-author/references/input-and-scroll.yaml +17 -0
  71. package/skills/ios/ios-maestro-flow-author/references/modal-and-dismiss.yaml +14 -0
  72. package/skills/ios/ios-maestro-flow-author/references/onboarding-flow.yaml +16 -0
  73. package/skills/ios/ios-maestro-flow-author/references/tab-navigation.yaml +13 -0
  74. package/skills/ios/ios-maestro-flow-author/references/tap-and-assert.yaml +9 -0
  75. package/skills/ios/swift-accessibility/LICENSE +21 -0
  76. package/skills/ios/swift-accessibility/SKILL.md +371 -0
  77. package/skills/ios/swift-accessibility/examples/before-after-appkit.md +446 -0
  78. package/skills/ios/swift-accessibility/examples/before-after-swiftui.md +441 -0
  79. package/skills/ios/swift-accessibility/examples/before-after-uikit.md +464 -0
  80. package/skills/ios/swift-accessibility/references/assistive-access.md +441 -0
  81. package/skills/ios/swift-accessibility/references/display-settings.md +491 -0
  82. package/skills/ios/swift-accessibility/references/dynamic-type.md +420 -0
  83. package/skills/ios/swift-accessibility/references/media-accessibility.md +421 -0
  84. package/skills/ios/swift-accessibility/references/motor-input.md +393 -0
  85. package/skills/ios/swift-accessibility/references/nutrition-labels.md +362 -0
  86. package/skills/ios/swift-accessibility/references/platform-specifics.md +515 -0
  87. package/skills/ios/swift-accessibility/references/semantic-structure.md +585 -0
  88. package/skills/ios/swift-accessibility/references/testing-auditing.md +507 -0
  89. package/skills/ios/swift-accessibility/references/voice-control.md +317 -0
  90. package/skills/ios/swift-accessibility/references/voiceover-swiftui.md +584 -0
  91. package/skills/ios/swift-accessibility/references/voiceover-uikit.md +519 -0
  92. package/skills/ios/swift-accessibility/references/wcag-mapping.md +167 -0
  93. package/skills/ios/swift-accessibility/resources/audit-template.swift +128 -0
  94. package/skills/ios/swift-accessibility/resources/qa-checklist.md +258 -0
  95. package/skills/ios/swift-concurrency/LICENSE +21 -0
  96. package/skills/ios/swift-concurrency/SKILL.md +171 -0
  97. package/skills/ios/swift-concurrency/references/_index.md +50 -0
  98. package/skills/ios/swift-concurrency/references/actors.md +660 -0
  99. package/skills/ios/swift-concurrency/references/async-algorithms.md +847 -0
  100. package/skills/ios/swift-concurrency/references/async-await-basics.md +266 -0
  101. package/skills/ios/swift-concurrency/references/async-sequences.md +710 -0
  102. package/skills/ios/swift-concurrency/references/core-data.md +560 -0
  103. package/skills/ios/swift-concurrency/references/glossary.md +135 -0
  104. package/skills/ios/swift-concurrency/references/linting.md +155 -0
  105. package/skills/ios/swift-concurrency/references/memory-management.md +569 -0
  106. package/skills/ios/swift-concurrency/references/migration.md +1104 -0
  107. package/skills/ios/swift-concurrency/references/performance.md +593 -0
  108. package/skills/ios/swift-concurrency/references/sendable.md +598 -0
  109. package/skills/ios/swift-concurrency/references/tasks.md +636 -0
  110. package/skills/ios/swift-concurrency/references/testing.md +592 -0
  111. package/skills/ios/swift-concurrency/references/threading.md +495 -0
  112. package/skills/ios/swift-security-expert/LICENSE +21 -0
  113. package/skills/ios/swift-security-expert/SKILL.md +470 -0
  114. package/skills/ios/swift-security-expert/references/biometric-authentication.md +565 -0
  115. package/skills/ios/swift-security-expert/references/certificate-trust.md +592 -0
  116. package/skills/ios/swift-security-expert/references/common-anti-patterns.md +690 -0
  117. package/skills/ios/swift-security-expert/references/compliance-owasp-mapping.md +537 -0
  118. package/skills/ios/swift-security-expert/references/credential-storage-patterns.md +721 -0
  119. package/skills/ios/swift-security-expert/references/cryptokit-public-key.md +505 -0
  120. package/skills/ios/swift-security-expert/references/cryptokit-symmetric.md +497 -0
  121. package/skills/ios/swift-security-expert/references/keychain-access-control.md +508 -0
  122. package/skills/ios/swift-security-expert/references/keychain-fundamentals.md +596 -0
  123. package/skills/ios/swift-security-expert/references/keychain-item-classes.md +476 -0
  124. package/skills/ios/swift-security-expert/references/keychain-sharing.md +458 -0
  125. package/skills/ios/swift-security-expert/references/migration-legacy-stores.md +727 -0
  126. package/skills/ios/swift-security-expert/references/secure-enclave.md +539 -0
  127. package/skills/ios/swift-security-expert/references/testing-security-code.md +781 -0
  128. package/skills/ios/swift-testing-expert/LICENSE +21 -0
  129. package/skills/ios/swift-testing-expert/SKILL.md +79 -0
  130. package/skills/ios/swift-testing-expert/references/_index.md +12 -0
  131. package/skills/ios/swift-testing-expert/references/async-testing-and-waiting.md +127 -0
  132. package/skills/ios/swift-testing-expert/references/expectations.md +145 -0
  133. package/skills/ios/swift-testing-expert/references/fundamentals.md +141 -0
  134. package/skills/ios/swift-testing-expert/references/migration-from-xctest.md +127 -0
  135. package/skills/ios/swift-testing-expert/references/parallelization-and-isolation.md +95 -0
  136. package/skills/ios/swift-testing-expert/references/parameterized-testing.md +284 -0
  137. package/skills/ios/swift-testing-expert/references/performance-and-best-practices.md +187 -0
  138. package/skills/ios/swift-testing-expert/references/traits-and-tags.md +114 -0
  139. package/skills/ios/swift-testing-expert/references/xcode-workflows.md +70 -0
  140. package/skills/ios/swiftdata-pro/LICENSE +21 -0
  141. package/skills/ios/swiftdata-pro/SKILL.md +102 -0
  142. package/skills/ios/swiftdata-pro/agents/openai.yaml +10 -0
  143. package/skills/ios/swiftdata-pro/assets/swiftdata-pro-icon.png +0 -0
  144. package/skills/ios/swiftdata-pro/assets/swiftdata-pro-icon.svg +29 -0
  145. package/skills/ios/swiftdata-pro/references/class-inheritance.md +104 -0
  146. package/skills/ios/swiftdata-pro/references/cloudkit.md +10 -0
  147. package/skills/ios/swiftdata-pro/references/core-rules.md +20 -0
  148. package/skills/ios/swiftdata-pro/references/indexing.md +27 -0
  149. package/skills/ios/swiftdata-pro/references/predicates.md +73 -0
  150. package/skills/ios/swiftui-design-principles/AGENTS.md +21 -0
  151. package/skills/ios/swiftui-design-principles/LICENSE +21 -0
  152. package/skills/ios/swiftui-design-principles/README.md +41 -0
  153. package/skills/ios/swiftui-design-principles/SKILL.md +605 -0
  154. package/skills/ios/swiftui-design-principles/metadata.json +10 -0
  155. package/skills/ios/swiftui-liquid-glass/LICENSE +21 -0
  156. package/skills/ios/swiftui-liquid-glass/SKILL.md +95 -0
  157. package/skills/ios/swiftui-liquid-glass/agents/openai.yaml +4 -0
  158. package/skills/ios/swiftui-liquid-glass/references/liquid-glass.md +280 -0
  159. package/skills/ios/swiftui-performance-audit/LICENSE +21 -0
  160. package/skills/ios/swiftui-performance-audit/SKILL.md +111 -0
  161. package/skills/ios/swiftui-performance-audit/agents/openai.yaml +4 -0
  162. package/skills/ios/swiftui-performance-audit/references/code-smells.md +150 -0
  163. package/skills/ios/swiftui-performance-audit/references/demystify-swiftui-performance-wwdc23.md +46 -0
  164. package/skills/ios/swiftui-performance-audit/references/optimizing-swiftui-performance-instruments.md +29 -0
  165. package/skills/ios/swiftui-performance-audit/references/profiling-intake.md +44 -0
  166. package/skills/ios/swiftui-performance-audit/references/report-template.md +47 -0
  167. package/skills/ios/swiftui-performance-audit/references/understanding-hangs-in-your-app.md +33 -0
  168. package/skills/ios/swiftui-performance-audit/references/understanding-improving-swiftui-performance.md +52 -0
  169. package/skills/ios/swiftui-pro/LICENSE +21 -0
  170. package/skills/ios/swiftui-pro/SKILL.md +108 -0
  171. package/skills/ios/swiftui-pro/agents/openai.yaml +10 -0
  172. package/skills/ios/swiftui-pro/assets/swiftui-pro-icon.png +0 -0
  173. package/skills/ios/swiftui-pro/assets/swiftui-pro-icon.svg +29 -0
  174. package/skills/ios/swiftui-pro/references/accessibility.md +13 -0
  175. package/skills/ios/swiftui-pro/references/api.md +39 -0
  176. package/skills/ios/swiftui-pro/references/data.md +43 -0
  177. package/skills/ios/swiftui-pro/references/design.md +31 -0
  178. package/skills/ios/swiftui-pro/references/hygiene.md +9 -0
  179. package/skills/ios/swiftui-pro/references/navigation.md +14 -0
  180. package/skills/ios/swiftui-pro/references/performance.md +46 -0
  181. package/skills/ios/swiftui-pro/references/swift.md +56 -0
  182. package/skills/ios/swiftui-pro/references/views.md +35 -0
  183. package/skills/ios/swiftui-ui-patterns/LICENSE +21 -0
  184. package/skills/ios/swiftui-ui-patterns/SKILL.md +100 -0
  185. package/skills/ios/swiftui-ui-patterns/agents/openai.yaml +4 -0
  186. package/skills/ios/swiftui-ui-patterns/references/app-wiring.md +201 -0
  187. package/skills/ios/swiftui-ui-patterns/references/async-state.md +96 -0
  188. package/skills/ios/swiftui-ui-patterns/references/components-index.md +50 -0
  189. package/skills/ios/swiftui-ui-patterns/references/controls.md +57 -0
  190. package/skills/ios/swiftui-ui-patterns/references/deeplinks.md +66 -0
  191. package/skills/ios/swiftui-ui-patterns/references/focus.md +90 -0
  192. package/skills/ios/swiftui-ui-patterns/references/form.md +97 -0
  193. package/skills/ios/swiftui-ui-patterns/references/grids.md +71 -0
  194. package/skills/ios/swiftui-ui-patterns/references/haptics.md +71 -0
  195. package/skills/ios/swiftui-ui-patterns/references/input-toolbar.md +51 -0
  196. package/skills/ios/swiftui-ui-patterns/references/lightweight-clients.md +93 -0
  197. package/skills/ios/swiftui-ui-patterns/references/list.md +86 -0
  198. package/skills/ios/swiftui-ui-patterns/references/loading-placeholders.md +38 -0
  199. package/skills/ios/swiftui-ui-patterns/references/macos-settings.md +71 -0
  200. package/skills/ios/swiftui-ui-patterns/references/matched-transitions.md +59 -0
  201. package/skills/ios/swiftui-ui-patterns/references/media.md +73 -0
  202. package/skills/ios/swiftui-ui-patterns/references/menu-bar.md +101 -0
  203. package/skills/ios/swiftui-ui-patterns/references/navigationstack.md +159 -0
  204. package/skills/ios/swiftui-ui-patterns/references/overlay.md +45 -0
  205. package/skills/ios/swiftui-ui-patterns/references/performance.md +62 -0
  206. package/skills/ios/swiftui-ui-patterns/references/previews.md +48 -0
  207. package/skills/ios/swiftui-ui-patterns/references/scroll-reveal.md +133 -0
  208. package/skills/ios/swiftui-ui-patterns/references/scrollview.md +87 -0
  209. package/skills/ios/swiftui-ui-patterns/references/searchable.md +71 -0
  210. package/skills/ios/swiftui-ui-patterns/references/sheets.md +155 -0
  211. package/skills/ios/swiftui-ui-patterns/references/split-views.md +72 -0
  212. package/skills/ios/swiftui-ui-patterns/references/tabview.md +114 -0
  213. package/skills/ios/swiftui-ui-patterns/references/theming.md +71 -0
  214. package/skills/ios/swiftui-ui-patterns/references/title-menus.md +93 -0
  215. package/skills/ios/swiftui-ui-patterns/references/top-bar.md +49 -0
  216. package/skills/ios/swiftui-view-refactor/LICENSE +21 -0
  217. package/skills/ios/swiftui-view-refactor/SKILL.md +207 -0
  218. package/skills/ios/swiftui-view-refactor/agents/openai.yaml +4 -0
  219. package/skills/ios/swiftui-view-refactor/references/mv-patterns.md +161 -0
  220. package/skills/ios/widgetkit/LICENSE +131 -0
  221. package/skills/ios/widgetkit/SKILL.md +502 -0
  222. package/skills/ios/widgetkit/references/widgetkit-advanced.md +871 -0
@@ -0,0 +1,66 @@
1
+ # Deep links and navigation
2
+
3
+ ## Intent
4
+
5
+ Route external URLs into in-app destinations while falling back to system handling when needed.
6
+
7
+ ## Core patterns
8
+
9
+ - Centralize URL handling in the router (`handle(url:)`, `handleDeepLink(url:)`).
10
+ - Inject an `OpenURLAction` handler that delegates to the router.
11
+ - Use `.onOpenURL` for app scheme links and convert them to web URLs if needed.
12
+ - Let the router decide whether to navigate or open externally.
13
+
14
+ ## Example: router entry points
15
+
16
+ ```swift
17
+ @MainActor
18
+ final class RouterPath {
19
+ var path: [Route] = []
20
+ var urlHandler: ((URL) -> OpenURLAction.Result)?
21
+
22
+ func handle(url: URL) -> OpenURLAction.Result {
23
+ if isInternal(url) {
24
+ navigate(to: .status(id: url.lastPathComponent))
25
+ return .handled
26
+ }
27
+ return urlHandler?(url) ?? .systemAction
28
+ }
29
+
30
+ func handleDeepLink(url: URL) -> OpenURLAction.Result {
31
+ // Resolve federated URLs, then navigate.
32
+ navigate(to: .status(id: url.lastPathComponent))
33
+ return .handled
34
+ }
35
+ }
36
+ ```
37
+
38
+ ## Example: attach to a root view
39
+
40
+ ```swift
41
+ extension View {
42
+ func withLinkRouter(_ router: RouterPath) -> some View {
43
+ self
44
+ .environment(
45
+ \.openURL,
46
+ OpenURLAction { url in
47
+ router.handle(url: url)
48
+ }
49
+ )
50
+ .onOpenURL { url in
51
+ router.handleDeepLink(url: url)
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ ## Design choices to keep
58
+
59
+ - Keep URL parsing and decision logic inside the router.
60
+ - Avoid handling deep links in multiple places; one entry point is enough.
61
+ - Always provide a fallback to `OpenURLAction` or `UIApplication.shared.open`.
62
+
63
+ ## Pitfalls
64
+
65
+ - Don’t assume the URL is internal; validate first.
66
+ - Avoid blocking UI while resolving remote links; use `Task`.
@@ -0,0 +1,90 @@
1
+ # Focus handling and field chaining
2
+
3
+ ## Intent
4
+
5
+ Use `@FocusState` to control keyboard focus, chain fields, and coordinate focus across complex forms.
6
+
7
+ ## Core patterns
8
+
9
+ - Use an enum to represent focusable fields.
10
+ - Set initial focus in `onAppear`.
11
+ - Use `.onSubmit` to move focus to the next field.
12
+ - For dynamic lists of fields, use an enum with associated values (e.g., `.option(Int)`).
13
+
14
+ ## Example: single field focus
15
+
16
+ ```swift
17
+ struct AddServerView: View {
18
+ @State private var server = ""
19
+ @FocusState private var isServerFieldFocused: Bool
20
+
21
+ var body: some View {
22
+ Form {
23
+ TextField("Server", text: $server)
24
+ .focused($isServerFieldFocused)
25
+ }
26
+ .onAppear { isServerFieldFocused = true }
27
+ }
28
+ }
29
+ ```
30
+
31
+ ## Example: chained focus with enum
32
+
33
+ ```swift
34
+ struct EditTagView: View {
35
+ enum FocusField { case title, symbol, newTag }
36
+ @FocusState private var focusedField: FocusField?
37
+
38
+ var body: some View {
39
+ Form {
40
+ TextField("Title", text: $title)
41
+ .focused($focusedField, equals: .title)
42
+ .onSubmit { focusedField = .symbol }
43
+
44
+ TextField("Symbol", text: $symbol)
45
+ .focused($focusedField, equals: .symbol)
46
+ .onSubmit { focusedField = .newTag }
47
+ }
48
+ .onAppear { focusedField = .title }
49
+ }
50
+ }
51
+ ```
52
+
53
+ ## Example: dynamic focus for variable fields
54
+
55
+ ```swift
56
+ struct PollView: View {
57
+ enum FocusField: Hashable { case option(Int) }
58
+ @FocusState private var focused: FocusField?
59
+ @State private var options: [String] = ["", ""]
60
+ @State private var currentIndex = 0
61
+
62
+ var body: some View {
63
+ ForEach(options.indices, id: \.self) { index in
64
+ TextField("Option \(index + 1)", text: $options[index])
65
+ .focused($focused, equals: .option(index))
66
+ .onSubmit { addOption(at: index) }
67
+ }
68
+ .onAppear { focused = .option(0) }
69
+ }
70
+
71
+ private func addOption(at index: Int) {
72
+ options.append("")
73
+ currentIndex = index + 1
74
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
75
+ focused = .option(currentIndex)
76
+ }
77
+ }
78
+ }
79
+ ```
80
+
81
+ ## Design choices to keep
82
+
83
+ - Keep focus state local to the view that owns the fields.
84
+ - Use focus changes to drive UX (validation messages, helper UI).
85
+ - Pair with `.scrollDismissesKeyboard(...)` when using ScrollView/Form.
86
+
87
+ ## Pitfalls
88
+
89
+ - Don’t store focus state in shared objects; it is view-local.
90
+ - Avoid aggressive focus changes during animation; delay if needed.
@@ -0,0 +1,97 @@
1
+ # Form
2
+
3
+ ## Intent
4
+
5
+ Use `Form` for structured settings, grouped inputs, and action rows. This pattern keeps layout, spacing, and accessibility consistent for data entry screens.
6
+
7
+ ## Core patterns
8
+
9
+ - Wrap the form in a `NavigationStack` only when it is presented in a sheet or standalone view without an existing navigation context.
10
+ - Group related controls into `Section` blocks.
11
+ - Use `.scrollContentBackground(.hidden)` plus a custom background color when you need design-system colors.
12
+ - Apply `.formStyle(.grouped)` for grouped styling when appropriate.
13
+ - Use `@FocusState` to manage keyboard focus in input-heavy forms.
14
+
15
+ ## Example: settings-style form
16
+
17
+ ```swift
18
+ @MainActor
19
+ struct SettingsView: View {
20
+ @Environment(Theme.self) private var theme
21
+
22
+ var body: some View {
23
+ NavigationStack {
24
+ Form {
25
+ Section("General") {
26
+ NavigationLink("Display") { DisplaySettingsView() }
27
+ NavigationLink("Haptics") { HapticsSettingsView() }
28
+ }
29
+
30
+ Section("Account") {
31
+ Button("Edit profile") { /* open sheet */ }
32
+ .buttonStyle(.plain)
33
+ }
34
+ .listRowBackground(theme.primaryBackgroundColor)
35
+ }
36
+ .navigationTitle("Settings")
37
+ .navigationBarTitleDisplayMode(.inline)
38
+ .scrollContentBackground(.hidden)
39
+ .background(theme.secondaryBackgroundColor)
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ ## Example: modal form with validation
46
+
47
+ ```swift
48
+ @MainActor
49
+ struct AddRemoteServerView: View {
50
+ @Environment(\.dismiss) private var dismiss
51
+ @Environment(Theme.self) private var theme
52
+
53
+ @State private var server: String = ""
54
+ @State private var isValid = false
55
+ @FocusState private var isServerFieldFocused: Bool
56
+
57
+ var body: some View {
58
+ NavigationStack {
59
+ Form {
60
+ TextField("Server URL", text: $server)
61
+ .keyboardType(.URL)
62
+ .textInputAutocapitalization(.never)
63
+ .autocorrectionDisabled()
64
+ .focused($isServerFieldFocused)
65
+ .listRowBackground(theme.primaryBackgroundColor)
66
+
67
+ Button("Add") {
68
+ guard isValid else { return }
69
+ dismiss()
70
+ }
71
+ .disabled(!isValid)
72
+ .listRowBackground(theme.primaryBackgroundColor)
73
+ }
74
+ .formStyle(.grouped)
75
+ .navigationTitle("Add Server")
76
+ .navigationBarTitleDisplayMode(.inline)
77
+ .scrollContentBackground(.hidden)
78
+ .background(theme.secondaryBackgroundColor)
79
+ .scrollDismissesKeyboard(.immediately)
80
+ .toolbar { CancelToolbarItem() }
81
+ .onAppear { isServerFieldFocused = true }
82
+ }
83
+ }
84
+ }
85
+ ```
86
+
87
+ ## Design choices to keep
88
+
89
+ - Prefer `Form` over custom stacks for settings and input screens.
90
+ - Keep rows tappable by using `.contentShape(Rectangle())` and `.buttonStyle(.plain)` on row buttons.
91
+ - Use list row backgrounds to keep section styling consistent with your theme.
92
+
93
+ ## Pitfalls
94
+
95
+ - Avoid heavy custom layouts inside a `Form`; it can lead to spacing issues.
96
+ - If you need highly custom layouts, prefer `ScrollView` + `VStack`.
97
+ - Don’t mix multiple background strategies; pick either default Form styling or custom colors.
@@ -0,0 +1,71 @@
1
+ # Grids
2
+
3
+ ## Intent
4
+
5
+ Use `LazyVGrid` for icon pickers, media galleries, and dense visual selections where items align in columns.
6
+
7
+ ## Core patterns
8
+
9
+ - Use `.adaptive` columns for layouts that should scale across device sizes.
10
+ - Use multiple `.flexible` columns when you want a fixed column count.
11
+ - Keep spacing consistent and small to avoid uneven gutters.
12
+ - Use `GeometryReader` inside grid cells when you need square thumbnails.
13
+
14
+ ## Example: adaptive icon grid
15
+
16
+ ```swift
17
+ let columns = [GridItem(.adaptive(minimum: 120, maximum: 1024))]
18
+
19
+ LazyVGrid(columns: columns, spacing: 6) {
20
+ ForEach(icons) { icon in
21
+ Button {
22
+ select(icon)
23
+ } label: {
24
+ ZStack(alignment: .bottomTrailing) {
25
+ Image(icon.previewName)
26
+ .resizable()
27
+ .aspectRatio(contentMode: .fit)
28
+ .cornerRadius(6)
29
+ if icon.isSelected {
30
+ Image(systemName: "checkmark.seal.fill")
31
+ .padding(4)
32
+ .tint(.green)
33
+ }
34
+ }
35
+ }
36
+ .buttonStyle(.plain)
37
+ }
38
+ }
39
+ ```
40
+
41
+ ## Example: fixed 3-column media grid
42
+
43
+ ```swift
44
+ LazyVGrid(
45
+ columns: [
46
+ .init(.flexible(minimum: 100), spacing: 4),
47
+ .init(.flexible(minimum: 100), spacing: 4),
48
+ .init(.flexible(minimum: 100), spacing: 4),
49
+ ],
50
+ spacing: 4
51
+ ) {
52
+ ForEach(items) { item in
53
+ GeometryReader { proxy in
54
+ ThumbnailView(item: item)
55
+ .frame(width: proxy.size.width, height: proxy.size.width)
56
+ }
57
+ .aspectRatio(1, contentMode: .fit)
58
+ }
59
+ }
60
+ ```
61
+
62
+ ## Design choices to keep
63
+
64
+ - Use `LazyVGrid` for large collections; avoid non-lazy grids for big sets.
65
+ - Keep tap targets full-bleed using `.contentShape(Rectangle())` when needed.
66
+ - Prefer adaptive grids for settings pickers and flexible layouts.
67
+
68
+ ## Pitfalls
69
+
70
+ - Avoid heavy overlays in every grid cell; it can be expensive.
71
+ - Don’t nest grids inside other grids without a clear reason.
@@ -0,0 +1,71 @@
1
+ # Haptics
2
+
3
+ ## Intent
4
+
5
+ Use haptics sparingly to reinforce user actions (tab selection, refresh, success/error) and respect user preferences.
6
+
7
+ ## Core patterns
8
+
9
+ - Centralize haptic triggers in a `HapticManager` or similar utility.
10
+ - Gate haptics behind user preferences and hardware support.
11
+ - Use distinct types for different UX moments (selection vs. notification vs. refresh).
12
+
13
+ ## Example: simple haptic manager
14
+
15
+ ```swift
16
+ @MainActor
17
+ final class HapticManager {
18
+ static let shared = HapticManager()
19
+
20
+ enum HapticType {
21
+ case buttonPress
22
+ case tabSelection
23
+ case dataRefresh(intensity: CGFloat)
24
+ case notification(UINotificationFeedbackGenerator.FeedbackType)
25
+ }
26
+
27
+ private let selectionGenerator = UISelectionFeedbackGenerator()
28
+ private let impactGenerator = UIImpactFeedbackGenerator(style: .heavy)
29
+ private let notificationGenerator = UINotificationFeedbackGenerator()
30
+
31
+ private init() { selectionGenerator.prepare() }
32
+
33
+ func fire(_ type: HapticType, isEnabled: Bool) {
34
+ guard isEnabled else { return }
35
+ switch type {
36
+ case .buttonPress:
37
+ impactGenerator.impactOccurred()
38
+ case .tabSelection:
39
+ selectionGenerator.selectionChanged()
40
+ case let .dataRefresh(intensity):
41
+ impactGenerator.impactOccurred(intensity: intensity)
42
+ case let .notification(style):
43
+ notificationGenerator.notificationOccurred(style)
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ ## Example: usage
50
+
51
+ ```swift
52
+ Button("Save") {
53
+ HapticManager.shared.fire(.notification(.success), isEnabled: preferences.hapticsEnabled)
54
+ }
55
+
56
+ TabView(selection: $selectedTab) { /* tabs */ }
57
+ .onChange(of: selectedTab) { _, _ in
58
+ HapticManager.shared.fire(.tabSelection, isEnabled: preferences.hapticTabSelectionEnabled)
59
+ }
60
+ ```
61
+
62
+ ## Design choices to keep
63
+
64
+ - Haptics should be subtle and not fire on every tiny interaction.
65
+ - Respect user preferences (toggle to disable).
66
+ - Keep haptic triggers close to the user action, not deep in data layers.
67
+
68
+ ## Pitfalls
69
+
70
+ - Avoid firing multiple haptics in quick succession.
71
+ - Do not assume haptics are available; check support.
@@ -0,0 +1,51 @@
1
+ # Input toolbar (bottom anchored)
2
+
3
+ ## Intent
4
+
5
+ Use a bottom-anchored input bar for chat, composer, or quick actions without fighting the keyboard.
6
+
7
+ ## Core patterns
8
+
9
+ - Use `.safeAreaInset(edge: .bottom)` to anchor the toolbar above the keyboard.
10
+ - Keep the main content in a `ScrollView` or `List`.
11
+ - Drive focus with `@FocusState` and set initial focus when needed.
12
+ - Avoid embedding the input bar inside the scroll content; keep it separate.
13
+
14
+ ## Example: scroll view + bottom input
15
+
16
+ ```swift
17
+ @MainActor
18
+ struct ConversationView: View {
19
+ @FocusState private var isInputFocused: Bool
20
+
21
+ var body: some View {
22
+ ScrollViewReader { _ in
23
+ ScrollView {
24
+ LazyVStack {
25
+ ForEach(messages) { message in
26
+ MessageRow(message: message)
27
+ }
28
+ }
29
+ .padding(.horizontal, .layoutPadding)
30
+ }
31
+ .safeAreaInset(edge: .bottom) {
32
+ InputBar(text: $draft)
33
+ .focused($isInputFocused)
34
+ }
35
+ .scrollDismissesKeyboard(.interactively)
36
+ .onAppear { isInputFocused = true }
37
+ }
38
+ }
39
+ }
40
+ ```
41
+
42
+ ## Design choices to keep
43
+
44
+ - Keep the input bar visually separated from the scrollable content.
45
+ - Use `.scrollDismissesKeyboard(.interactively)` for chat-like screens.
46
+ - Ensure send actions are reachable via keyboard return or a clear button.
47
+
48
+ ## Pitfalls
49
+
50
+ - Avoid placing the input view inside the scroll stack; it will jump with content.
51
+ - Avoid nested scroll views that fight for drag gestures.
@@ -0,0 +1,93 @@
1
+ # Lightweight Clients (Closure-Based)
2
+
3
+ Use this pattern to keep networking or service dependencies simple and testable without introducing a full view model or heavy DI framework. It works well for SwiftUI apps where you want a small, composable API surface that can be swapped in previews/tests.
4
+
5
+ ## Intent
6
+ - Provide a tiny "client" type made of async closures.
7
+ - Keep business logic in a store or feature layer, not the view.
8
+ - Enable easy stubbing in previews/tests.
9
+
10
+ ## Minimal shape
11
+ ```swift
12
+ struct SomeClient {
13
+ var fetchItems: (_ limit: Int) async throws -> [Item]
14
+ var search: (_ query: String, _ limit: Int) async throws -> [Item]
15
+ }
16
+
17
+ extension SomeClient {
18
+ static func live(baseURL: URL = URL(string: "https://example.com")!) -> SomeClient {
19
+ let session = URLSession.shared
20
+ return SomeClient(
21
+ fetchItems: { limit in
22
+ // build URL, call session, decode
23
+ },
24
+ search: { query, limit in
25
+ // build URL, call session, decode
26
+ }
27
+ )
28
+ }
29
+ }
30
+ ```
31
+
32
+ ## Usage pattern
33
+ ```swift
34
+ @MainActor
35
+ @Observable final class ItemsStore {
36
+ enum LoadState { case idle, loading, loaded, failed(String) }
37
+
38
+ var items: [Item] = []
39
+ var state: LoadState = .idle
40
+ private let client: SomeClient
41
+
42
+ init(client: SomeClient) {
43
+ self.client = client
44
+ }
45
+
46
+ func load(limit: Int = 20) async {
47
+ state = .loading
48
+ do {
49
+ items = try await client.fetchItems(limit)
50
+ state = .loaded
51
+ } catch {
52
+ state = .failed(error.localizedDescription)
53
+ }
54
+ }
55
+ }
56
+ ```
57
+
58
+ ```swift
59
+ struct ContentView: View {
60
+ @Environment(ItemsStore.self) private var store
61
+
62
+ var body: some View {
63
+ List(store.items) { item in
64
+ Text(item.title)
65
+ }
66
+ .task { await store.load() }
67
+ }
68
+ }
69
+ ```
70
+
71
+ ```swift
72
+ @main
73
+ struct MyApp: App {
74
+ @State private var store = ItemsStore(client: .live())
75
+
76
+ var body: some Scene {
77
+ WindowGroup {
78
+ ContentView()
79
+ .environment(store)
80
+ }
81
+ }
82
+ }
83
+ ```
84
+
85
+ ## Guidance
86
+ - Keep decoding and URL-building in the client; keep state changes in the store.
87
+ - Make the store accept the client in `init` and keep it private.
88
+ - Avoid global singletons; use `.environment` for store injection.
89
+ - If you need multiple variants (mock/stub), add `static func mock(...)`.
90
+
91
+ ## Pitfalls
92
+ - Don’t put UI state in the client; keep state in the store.
93
+ - Don’t capture `self` or view state in the client closures.
@@ -0,0 +1,86 @@
1
+ # List and Section
2
+
3
+ ## Intent
4
+
5
+ Use `List` for feed-style content and settings-style rows where built-in row reuse, selection, and accessibility matter.
6
+
7
+ ## Core patterns
8
+
9
+ - Prefer `List` for long, vertically scrolling content with repeated rows.
10
+ - Use `Section` headers to group related rows.
11
+ - Pair with `ScrollViewReader` when you need scroll-to-top or jump-to-id.
12
+ - Use `.listStyle(.plain)` for modern feed layouts.
13
+ - Use `.listStyle(.grouped)` for multi-section discovery/search pages where section grouping helps.
14
+ - Apply `.scrollContentBackground(.hidden)` + a custom background when you need a themed surface.
15
+ - Use `.listRowInsets(...)` and `.listRowSeparator(.hidden)` to tune row spacing and separators.
16
+ - Use `.environment(\\.defaultMinListRowHeight, ...)` to control dense list layouts.
17
+
18
+ ## Example: feed list with scroll-to-top
19
+
20
+ ```swift
21
+ @MainActor
22
+ struct TimelineListView: View {
23
+ @Environment(\.selectedTabScrollToTop) private var selectedTabScrollToTop
24
+ @State private var scrollToId: String?
25
+
26
+ var body: some View {
27
+ ScrollViewReader { proxy in
28
+ List {
29
+ ForEach(items) { item in
30
+ TimelineRow(item: item)
31
+ .id(item.id)
32
+ .listRowInsets(.init(top: 12, leading: 16, bottom: 6, trailing: 16))
33
+ .listRowSeparator(.hidden)
34
+ }
35
+ }
36
+ .listStyle(.plain)
37
+ .environment(\\.defaultMinListRowHeight, 1)
38
+ .onChange(of: scrollToId) { _, newValue in
39
+ if let newValue {
40
+ proxy.scrollTo(newValue, anchor: .top)
41
+ scrollToId = nil
42
+ }
43
+ }
44
+ .onChange(of: selectedTabScrollToTop) { _, newValue in
45
+ if newValue == 0 {
46
+ withAnimation {
47
+ proxy.scrollTo(ScrollToView.Constants.scrollToTop, anchor: .top)
48
+ }
49
+ }
50
+ }
51
+ }
52
+ }
53
+ }
54
+ ```
55
+
56
+ ## Example: settings-style list
57
+
58
+ ```swift
59
+ @MainActor
60
+ struct SettingsView: View {
61
+ var body: some View {
62
+ List {
63
+ Section("General") {
64
+ NavigationLink("Display") { DisplaySettingsView() }
65
+ NavigationLink("Haptics") { HapticsSettingsView() }
66
+ }
67
+ Section("Account") {
68
+ Button("Sign Out", role: .destructive) {}
69
+ }
70
+ }
71
+ .listStyle(.insetGrouped)
72
+ }
73
+ }
74
+ ```
75
+
76
+ ## Design choices to keep
77
+
78
+ - Use `List` for dynamic feeds, settings, and any UI where row semantics help.
79
+ - Use stable IDs for rows to keep animations and scroll positioning reliable.
80
+ - Prefer `.contentShape(Rectangle())` on rows that should be tappable end-to-end.
81
+ - Use `.refreshable` for pull-to-refresh feeds when the data source supports it.
82
+
83
+ ## Pitfalls
84
+
85
+ - Avoid heavy custom layouts inside a `List` row; use `ScrollView` + `LazyVStack` instead.
86
+ - Be careful mixing `List` and nested `ScrollView`; it can cause gesture conflicts.
@@ -0,0 +1,38 @@
1
+ # Loading & Placeholders
2
+
3
+ Use this when a view needs a consistent loading state (skeletons, redaction, empty state) without blocking interaction.
4
+
5
+ ## Patterns to prefer
6
+
7
+ - **Redacted placeholders** for list/detail content to preserve layout while loading.
8
+ - **ContentUnavailableView** for empty or error states after loading completes.
9
+ - **ProgressView** only for short, global operations (use sparingly in content-heavy screens).
10
+
11
+ ## Recommended approach
12
+
13
+ 1. Keep the real layout, render placeholder data, then apply `.redacted(reason: .placeholder)`.
14
+ 2. For lists, show a fixed number of placeholder rows (avoid infinite spinners).
15
+ 3. Switch to `ContentUnavailableView` when load finishes but data is empty.
16
+
17
+ ## Pitfalls
18
+
19
+ - Don’t animate layout shifts during redaction; keep frames stable.
20
+ - Avoid nesting multiple spinners; use one loading indicator per section.
21
+ - Keep placeholder count small (3–6) to reduce jank on low-end devices.
22
+
23
+ ## Minimal usage
24
+
25
+ ```swift
26
+ VStack {
27
+ if isLoading {
28
+ ForEach(0..<3, id: \.self) { _ in
29
+ RowView(model: .placeholder())
30
+ }
31
+ .redacted(reason: .placeholder)
32
+ } else if items.isEmpty {
33
+ ContentUnavailableView("No items", systemImage: "tray")
34
+ } else {
35
+ ForEach(items) { item in RowView(model: item) }
36
+ }
37
+ }
38
+ ```