claudecode-omc 5.6.6 → 5.6.8

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 (179) hide show
  1. package/.local/skills/THIRD_PARTY_LICENSES/AvdLee-SwiftUI-Agent-Skill.LICENSE +21 -0
  2. package/.local/skills/THIRD_PARTY_LICENSES/Dimillian-Skills.LICENSE +21 -0
  3. package/.local/skills/THIRD_PARTY_LICENSES/README.md +36 -0
  4. package/.local/skills/THIRD_PARTY_LICENSES/twostraws-swiftui-agent-skill.LICENSE +21 -0
  5. package/.local/skills/h5-to-swiftui/SKILL.md +201 -0
  6. package/.local/skills/h5-to-swiftui/assets/calibration/README.md +176 -0
  7. package/.local/skills/h5-to-swiftui/assets/calibration/h5-twin/index.html +52 -0
  8. package/.local/skills/h5-to-swiftui/assets/calibration/h5-twin/style.css +133 -0
  9. package/.local/skills/h5-to-swiftui/assets/calibration/swiftui-twin/Package.swift +26 -0
  10. package/.local/skills/h5-to-swiftui/assets/calibration/swiftui-twin/Sources/CalibrationScreen/CalibrationScreen.swift +142 -0
  11. package/.local/skills/h5-to-swiftui/assets/calibration/swiftui-twin-divergent/Package.swift +32 -0
  12. package/.local/skills/h5-to-swiftui/assets/calibration/swiftui-twin-divergent/Sources/CalibrationScreenDivergent/CalibrationScreenDivergent.swift +122 -0
  13. package/.local/skills/h5-to-swiftui/assets/calibration/tokens.json +42 -0
  14. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/index.html +14 -0
  15. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/package.json +20 -0
  16. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/public/api/articles/001.json +96 -0
  17. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/public/api/articles/index.json +89 -0
  18. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/App.jsx +22 -0
  19. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/App.module.css +11 -0
  20. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/ArticleCard.jsx +53 -0
  21. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/ArticleCard.module.css +139 -0
  22. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/NavBar.jsx +37 -0
  23. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/NavBar.module.css +72 -0
  24. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/TagCloud.jsx +30 -0
  25. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/TagCloud.module.css +50 -0
  26. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/TrendChart.jsx +159 -0
  27. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/TrendChart.module.css +21 -0
  28. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/main.jsx +12 -0
  29. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/screens/ArticleScreen.jsx +182 -0
  30. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/screens/ArticleScreen.module.css +294 -0
  31. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/screens/FeedScreen.jsx +147 -0
  32. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/screens/FeedScreen.module.css +161 -0
  33. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/styles/global.css +50 -0
  34. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/styles/tokens.css +103 -0
  35. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/vite.config.js +6 -0
  36. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/data/tasks.js +67 -0
  37. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/index.html +26 -0
  38. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/router.js +73 -0
  39. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/screens/detail.js +164 -0
  40. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/screens/home.js +53 -0
  41. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/screens/list.js +87 -0
  42. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/styles/app.css +342 -0
  43. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/styles/tokens.css +68 -0
  44. package/.local/skills/h5-to-swiftui/references/css-to-swiftui-map.md +205 -0
  45. package/.local/skills/h5-to-swiftui/references/design-token-extraction.md +209 -0
  46. package/.local/skills/h5-to-swiftui/references/high-risk-triage.md +209 -0
  47. package/.local/skills/h5-to-swiftui/references/render-equivalence-calibration.md +193 -0
  48. package/.local/skills/h5-to-swiftui/references/stack-detection.md +160 -0
  49. package/.local/skills/h5-to-swiftui/references/visual-diff-loop-protocol.md +365 -0
  50. package/.local/skills/h5-to-swiftui/scripts/_calib-consts.mjs +150 -0
  51. package/.local/skills/h5-to-swiftui/scripts/_imglib.mjs +547 -0
  52. package/.local/skills/h5-to-swiftui/scripts/_provenance.mjs +123 -0
  53. package/.local/skills/h5-to-swiftui/scripts/calibrate-render.mjs +625 -0
  54. package/.local/skills/h5-to-swiftui/scripts/capture-reference.mjs +386 -0
  55. package/.local/skills/h5-to-swiftui/scripts/detect-stack.mjs +305 -0
  56. package/.local/skills/h5-to-swiftui/scripts/evaluate-convergence.mjs +1093 -0
  57. package/.local/skills/h5-to-swiftui/scripts/extract-tokens.mjs +600 -0
  58. package/.local/skills/h5-to-swiftui/scripts/mark-overlay.mjs +379 -0
  59. package/.local/skills/h5-to-swiftui/scripts/pixel-diff.mjs +530 -0
  60. package/.local/skills/h5-to-swiftui/scripts/sim-screenshot.sh +544 -0
  61. package/.local/skills/ios-debugger-agent/SKILL.md +51 -0
  62. package/.local/skills/ios-debugger-agent/agents/openai.yaml +4 -0
  63. package/.local/skills/swift-concurrency-expert/SKILL.md +105 -0
  64. package/.local/skills/swift-concurrency-expert/agents/openai.yaml +4 -0
  65. package/.local/skills/swift-concurrency-expert/references/approachable-concurrency.md +63 -0
  66. package/.local/skills/swift-concurrency-expert/references/swift-6-2-concurrency.md +272 -0
  67. package/.local/skills/swift-concurrency-expert/references/swiftui-concurrency-tour-wwdc.md +33 -0
  68. package/.local/skills/swiftui-expert-skill/SKILL.md +162 -0
  69. package/.local/skills/swiftui-expert-skill/references/accessibility-patterns.md +215 -0
  70. package/.local/skills/swiftui-expert-skill/references/animation-advanced.md +403 -0
  71. package/.local/skills/swiftui-expert-skill/references/animation-basics.md +284 -0
  72. package/.local/skills/swiftui-expert-skill/references/animation-transitions.md +326 -0
  73. package/.local/skills/swiftui-expert-skill/references/charts-accessibility.md +135 -0
  74. package/.local/skills/swiftui-expert-skill/references/charts.md +602 -0
  75. package/.local/skills/swiftui-expert-skill/references/focus-patterns.md +299 -0
  76. package/.local/skills/swiftui-expert-skill/references/image-optimization.md +203 -0
  77. package/.local/skills/swiftui-expert-skill/references/latest-apis.md +488 -0
  78. package/.local/skills/swiftui-expert-skill/references/layout-best-practices.md +266 -0
  79. package/.local/skills/swiftui-expert-skill/references/liquid-glass.md +423 -0
  80. package/.local/skills/swiftui-expert-skill/references/list-patterns.md +446 -0
  81. package/.local/skills/swiftui-expert-skill/references/macos-scenes.md +318 -0
  82. package/.local/skills/swiftui-expert-skill/references/macos-views.md +357 -0
  83. package/.local/skills/swiftui-expert-skill/references/macos-window-styling.md +303 -0
  84. package/.local/skills/swiftui-expert-skill/references/performance-patterns.md +403 -0
  85. package/.local/skills/swiftui-expert-skill/references/scroll-patterns.md +293 -0
  86. package/.local/skills/swiftui-expert-skill/references/sheet-navigation-patterns.md +363 -0
  87. package/.local/skills/swiftui-expert-skill/references/state-management.md +388 -0
  88. package/.local/skills/swiftui-expert-skill/references/text-patterns.md +32 -0
  89. package/.local/skills/swiftui-expert-skill/references/trace-analysis.md +295 -0
  90. package/.local/skills/swiftui-expert-skill/references/trace-recording.md +134 -0
  91. package/.local/skills/swiftui-expert-skill/references/view-structure.md +780 -0
  92. package/.local/skills/swiftui-expert-skill/scripts/__pycache__/analyze_trace.cpython-313.pyc +0 -0
  93. package/.local/skills/swiftui-expert-skill/scripts/__pycache__/record_trace.cpython-313.pyc +0 -0
  94. package/.local/skills/swiftui-expert-skill/scripts/analyze_trace.py +301 -0
  95. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__init__.py +1 -0
  96. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/__init__.cpython-313.pyc +0 -0
  97. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/causes.cpython-313.pyc +0 -0
  98. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/correlate.cpython-313.pyc +0 -0
  99. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/events.cpython-313.pyc +0 -0
  100. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/hangs.cpython-313.pyc +0 -0
  101. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/hitches.cpython-313.pyc +0 -0
  102. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/summary.cpython-313.pyc +0 -0
  103. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/swiftui.cpython-313.pyc +0 -0
  104. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/time_profiler.cpython-313.pyc +0 -0
  105. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/xctrace.cpython-313.pyc +0 -0
  106. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/xml_utils.cpython-313.pyc +0 -0
  107. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/causes.py +187 -0
  108. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/correlate.py +179 -0
  109. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/events.py +291 -0
  110. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/hangs.py +108 -0
  111. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/hitches.py +145 -0
  112. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/summary.py +243 -0
  113. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/swiftui.py +195 -0
  114. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/time_profiler.py +135 -0
  115. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/xctrace.py +117 -0
  116. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/xml_utils.py +224 -0
  117. package/.local/skills/swiftui-expert-skill/scripts/record_trace.py +252 -0
  118. package/.local/skills/swiftui-liquid-glass/SKILL.md +90 -0
  119. package/.local/skills/swiftui-liquid-glass/agents/openai.yaml +4 -0
  120. package/.local/skills/swiftui-liquid-glass/references/liquid-glass.md +280 -0
  121. package/.local/skills/swiftui-performance-audit/SKILL.md +106 -0
  122. package/.local/skills/swiftui-performance-audit/agents/openai.yaml +4 -0
  123. package/.local/skills/swiftui-performance-audit/references/code-smells.md +150 -0
  124. package/.local/skills/swiftui-performance-audit/references/demystify-swiftui-performance-wwdc23.md +46 -0
  125. package/.local/skills/swiftui-performance-audit/references/optimizing-swiftui-performance-instruments.md +29 -0
  126. package/.local/skills/swiftui-performance-audit/references/profiling-intake.md +44 -0
  127. package/.local/skills/swiftui-performance-audit/references/report-template.md +47 -0
  128. package/.local/skills/swiftui-performance-audit/references/understanding-hangs-in-your-app.md +33 -0
  129. package/.local/skills/swiftui-performance-audit/references/understanding-improving-swiftui-performance.md +52 -0
  130. package/.local/skills/swiftui-pro/SKILL.md +108 -0
  131. package/.local/skills/swiftui-pro/agents/openai.yaml +10 -0
  132. package/.local/skills/swiftui-pro/assets/swiftui-pro-icon.png +0 -0
  133. package/.local/skills/swiftui-pro/assets/swiftui-pro-icon.svg +29 -0
  134. package/.local/skills/swiftui-pro/references/accessibility.md +13 -0
  135. package/.local/skills/swiftui-pro/references/api.md +39 -0
  136. package/.local/skills/swiftui-pro/references/data.md +43 -0
  137. package/.local/skills/swiftui-pro/references/design.md +32 -0
  138. package/.local/skills/swiftui-pro/references/hygiene.md +9 -0
  139. package/.local/skills/swiftui-pro/references/navigation.md +14 -0
  140. package/.local/skills/swiftui-pro/references/performance.md +46 -0
  141. package/.local/skills/swiftui-pro/references/swift.md +56 -0
  142. package/.local/skills/swiftui-pro/references/views.md +36 -0
  143. package/.local/skills/swiftui-ui-patterns/SKILL.md +95 -0
  144. package/.local/skills/swiftui-ui-patterns/agents/openai.yaml +4 -0
  145. package/.local/skills/swiftui-ui-patterns/references/app-wiring.md +201 -0
  146. package/.local/skills/swiftui-ui-patterns/references/async-state.md +96 -0
  147. package/.local/skills/swiftui-ui-patterns/references/components-index.md +50 -0
  148. package/.local/skills/swiftui-ui-patterns/references/controls.md +57 -0
  149. package/.local/skills/swiftui-ui-patterns/references/deeplinks.md +66 -0
  150. package/.local/skills/swiftui-ui-patterns/references/focus.md +90 -0
  151. package/.local/skills/swiftui-ui-patterns/references/form.md +97 -0
  152. package/.local/skills/swiftui-ui-patterns/references/grids.md +71 -0
  153. package/.local/skills/swiftui-ui-patterns/references/haptics.md +71 -0
  154. package/.local/skills/swiftui-ui-patterns/references/input-toolbar.md +51 -0
  155. package/.local/skills/swiftui-ui-patterns/references/lightweight-clients.md +93 -0
  156. package/.local/skills/swiftui-ui-patterns/references/list.md +86 -0
  157. package/.local/skills/swiftui-ui-patterns/references/loading-placeholders.md +38 -0
  158. package/.local/skills/swiftui-ui-patterns/references/macos-settings.md +71 -0
  159. package/.local/skills/swiftui-ui-patterns/references/matched-transitions.md +59 -0
  160. package/.local/skills/swiftui-ui-patterns/references/media.md +73 -0
  161. package/.local/skills/swiftui-ui-patterns/references/menu-bar.md +101 -0
  162. package/.local/skills/swiftui-ui-patterns/references/navigationstack.md +159 -0
  163. package/.local/skills/swiftui-ui-patterns/references/overlay.md +45 -0
  164. package/.local/skills/swiftui-ui-patterns/references/performance.md +62 -0
  165. package/.local/skills/swiftui-ui-patterns/references/previews.md +48 -0
  166. package/.local/skills/swiftui-ui-patterns/references/scroll-reveal.md +133 -0
  167. package/.local/skills/swiftui-ui-patterns/references/scrollview.md +87 -0
  168. package/.local/skills/swiftui-ui-patterns/references/searchable.md +71 -0
  169. package/.local/skills/swiftui-ui-patterns/references/sheets.md +155 -0
  170. package/.local/skills/swiftui-ui-patterns/references/split-views.md +72 -0
  171. package/.local/skills/swiftui-ui-patterns/references/tabview.md +114 -0
  172. package/.local/skills/swiftui-ui-patterns/references/theming.md +71 -0
  173. package/.local/skills/swiftui-ui-patterns/references/title-menus.md +93 -0
  174. package/.local/skills/swiftui-ui-patterns/references/top-bar.md +49 -0
  175. package/.local/skills/swiftui-view-refactor/SKILL.md +202 -0
  176. package/.local/skills/swiftui-view-refactor/agents/openai.yaml +4 -0
  177. package/.local/skills/swiftui-view-refactor/references/mv-patterns.md +161 -0
  178. package/bundled/manifest.json +1 -1
  179. package/package.json +1 -1
@@ -0,0 +1,45 @@
1
+ # Overlay and toasts
2
+
3
+ ## Intent
4
+
5
+ Use overlays for transient UI (toasts, banners, loaders) without affecting layout.
6
+
7
+ ## Core patterns
8
+
9
+ - Use `.overlay(alignment:)` to place global UI without changing the underlying layout.
10
+ - Keep overlays lightweight and dismissible.
11
+ - Use a dedicated `ToastCenter` (or similar) for global state if multiple features trigger toasts.
12
+
13
+ ## Example: toast overlay
14
+
15
+ ```swift
16
+ struct AppRootView: View {
17
+ @State private var toast: Toast?
18
+
19
+ var body: some View {
20
+ content
21
+ .overlay(alignment: .top) {
22
+ if let toast {
23
+ ToastView(toast: toast)
24
+ .transition(.move(edge: .top).combined(with: .opacity))
25
+ .onAppear {
26
+ DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
27
+ withAnimation { self.toast = nil }
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }
33
+ }
34
+ ```
35
+
36
+ ## Design choices to keep
37
+
38
+ - Prefer overlays for transient UI rather than embedding in layout stacks.
39
+ - Use transitions and short auto-dismiss timers.
40
+ - Keep the overlay aligned to a clear edge (`.top` or `.bottom`).
41
+
42
+ ## Pitfalls
43
+
44
+ - Avoid overlays that block all interaction unless explicitly needed.
45
+ - Don’t stack many overlays; use a queue or replace the current toast.
@@ -0,0 +1,62 @@
1
+ # Performance guardrails
2
+
3
+ ## Intent
4
+
5
+ Use these rules when a SwiftUI screen is large, scroll-heavy, frequently updated, or at risk of unnecessary recomputation.
6
+
7
+ ## Core rules
8
+
9
+ - Give `ForEach` and list content stable identity. Do not use unstable indices as identity when the collection can reorder or mutate.
10
+ - Keep expensive filtering, sorting, and formatting out of `body`; precompute or move it into a model/helper when it is not trivial.
11
+ - Narrow observation scope so only the views that read changing state need to update.
12
+ - Prefer lazy containers for larger scrolling content and extract subviews when only part of a screen changes frequently.
13
+ - Avoid swapping entire top-level view trees for small state changes; keep a stable root view and vary localized sections or modifiers.
14
+
15
+ ## Example: stable identity
16
+
17
+ ```swift
18
+ ForEach(items) { item in
19
+ Row(item: item)
20
+ }
21
+ ```
22
+
23
+ Prefer that over index-based identity when the collection can change order:
24
+
25
+ ```swift
26
+ ForEach(Array(items.enumerated()), id: \.offset) { _, item in
27
+ Row(item: item)
28
+ }
29
+ ```
30
+
31
+ ## Example: move expensive work out of body
32
+
33
+ ```swift
34
+ struct FeedView: View {
35
+ let items: [FeedItem]
36
+
37
+ private var sortedItems: [FeedItem] {
38
+ items.sorted(using: KeyPathComparator(\.createdAt, order: .reverse))
39
+ }
40
+
41
+ var body: some View {
42
+ List(sortedItems) { item in
43
+ FeedRow(item: item)
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ If the work is more expensive than a small derived property, move it into a model, store, or helper that updates less often.
50
+
51
+ ## When to investigate further
52
+
53
+ - Janky scrolling in long feeds or grids
54
+ - Typing lag from search or form validation
55
+ - Overly broad view updates when one small piece of state changes
56
+ - Large screens with many conditionals or repeated formatting work
57
+
58
+ ## Pitfalls
59
+
60
+ - Recomputing heavy transforms every render
61
+ - Observing a large object from many descendants when only one field matters
62
+ - Building custom scroll containers when `List`, `LazyVStack`, or `LazyHGrid` would already solve the problem
@@ -0,0 +1,48 @@
1
+ # Previews
2
+
3
+ ## Intent
4
+
5
+ Use previews to validate layout, state wiring, and injected dependencies without relying on a running app or live services.
6
+
7
+ ## Core rules
8
+
9
+ - Add `#Preview` coverage for the primary state plus important secondary states such as loading, empty, and error.
10
+ - Use deterministic fixtures, mocks, and sample data. Do not make previews depend on live network calls, real databases, or global singletons.
11
+ - Install required environment dependencies directly in the preview so the view can render in isolation.
12
+ - Keep preview setup close to the view until it becomes noisy; then extract lightweight preview helpers or fixtures.
13
+ - If a preview crashes, fix the state initialization or dependency wiring before expanding the feature further.
14
+
15
+ ## Example: simple preview states
16
+
17
+ ```swift
18
+ #Preview("Loaded") {
19
+ ProfileView(profile: .fixture)
20
+ }
21
+
22
+ #Preview("Empty") {
23
+ ProfileView(profile: nil)
24
+ }
25
+ ```
26
+
27
+ ## Example: preview with injected dependencies
28
+
29
+ ```swift
30
+ #Preview("Search results") {
31
+ SearchView()
32
+ .environment(SearchClient.preview(results: [.fixture, .fixture2]))
33
+ .environment(Theme.preview)
34
+ }
35
+ ```
36
+
37
+ ## Preview checklist
38
+
39
+ - Does the preview install every required environment dependency?
40
+ - Does it cover at least one success path and one non-happy path?
41
+ - Are fixtures stable and small enough to be read quickly?
42
+ - Can the preview render without network, auth, or app-global initialization?
43
+
44
+ ## Pitfalls
45
+
46
+ - Do not hide preview crashes by making dependencies optional if the production view requires them.
47
+ - Avoid huge inline fixtures when a named sample is easier to read.
48
+ - Do not couple previews to global shared singletons unless the project has no alternative.
@@ -0,0 +1,133 @@
1
+ # Scroll-reveal detail surfaces
2
+
3
+ ## Intent
4
+
5
+ Use this pattern when a detail screen has a primary surface first and secondary content behind it, and you want the user to reveal that secondary layer by scrolling or swiping instead of tapping a separate button.
6
+
7
+ Typical fits:
8
+
9
+ - media detail screens that reveal actions or metadata
10
+ - maps, cards, or canvases that transition into structured detail
11
+ - full-screen viewers with a second "actions" or "insights" page
12
+
13
+ ## Core pattern
14
+
15
+ Build the interaction as a paged vertical `ScrollView` with two sections:
16
+
17
+ 1. a primary section sized to the viewport
18
+ 2. a secondary section below it
19
+
20
+ Derive a normalized `progress` value from the vertical content offset and drive all visual changes from that one value.
21
+
22
+ Avoid treating the reveal as a separate gesture system unless scroll alone cannot express it.
23
+
24
+ ## Minimal structure
25
+
26
+ ```swift
27
+ private enum DetailSection: Hashable {
28
+ case primary
29
+ case secondary
30
+ }
31
+
32
+ struct DetailSurface: View {
33
+ @State private var revealProgress: CGFloat = 0
34
+ @State private var secondaryHeight: CGFloat = 1
35
+
36
+ var body: some View {
37
+ GeometryReader { geometry in
38
+ ScrollViewReader { proxy in
39
+ ScrollView(.vertical, showsIndicators: false) {
40
+ VStack(spacing: 0) {
41
+ PrimaryContent(progress: revealProgress)
42
+ .frame(height: geometry.size.height)
43
+ .id(DetailSection.primary)
44
+
45
+ SecondaryContent(progress: revealProgress)
46
+ .id(DetailSection.secondary)
47
+ .onGeometryChange(for: CGFloat.self) { geo in
48
+ geo.size.height
49
+ } action: { newHeight in
50
+ secondaryHeight = max(newHeight, 1)
51
+ }
52
+ }
53
+ .scrollTargetLayout()
54
+ }
55
+ .scrollTargetBehavior(.paging)
56
+ .onScrollGeometryChange(for: CGFloat.self, of: { scroll in
57
+ scroll.contentOffset.y + scroll.contentInsets.top
58
+ }) { _, offset in
59
+ revealProgress = (offset / secondaryHeight).clamped(to: 0...1)
60
+ }
61
+ .safeAreaInset(edge: .bottom) {
62
+ ChevronAffordance(progress: revealProgress) {
63
+ withAnimation(.smooth) {
64
+ let target: DetailSection = revealProgress < 0.5 ? .secondary : .primary
65
+ proxy.scrollTo(target, anchor: .top)
66
+ }
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
72
+ }
73
+ ```
74
+
75
+ ## Design choices to keep
76
+
77
+ - Make the primary section exactly viewport-sized when the interaction should feel like paging between states.
78
+ - Compute `progress` from real scroll offset, not from duplicated booleans like `isExpanded`, `isShowingSecondary`, and `isSnapped`.
79
+ - Use `progress` to drive `offset`, `opacity`, `blur`, `scaleEffect`, and toolbar state so the whole surface stays synchronized.
80
+ - Use `ScrollViewReader` for programmatic snapping from taps on the primary content or chevron affordances.
81
+ - Use `onScrollTargetVisibilityChange` when you need a settled section state for haptics, tooltip dismissal, analytics, or accessibility announcements.
82
+
83
+ ## Morphing a shared control
84
+
85
+ If a control appears to move from the primary surface into the secondary content, do not render two fully visible copies.
86
+
87
+ Instead:
88
+
89
+ - expose a source anchor in the primary area
90
+ - expose a destination anchor in the secondary area
91
+ - render one overlay that interpolates position and size using `progress`
92
+
93
+ ```swift
94
+ Color.clear
95
+ .anchorPreference(key: ControlAnchorKey.self, value: .bounds) { anchor in
96
+ ["source": anchor]
97
+ }
98
+
99
+ Color.clear
100
+ .anchorPreference(key: ControlAnchorKey.self, value: .bounds) { anchor in
101
+ ["destination": anchor]
102
+ }
103
+
104
+ .overlayPreferenceValue(ControlAnchorKey.self) { anchors in
105
+ MorphingControlOverlay(anchors: anchors, progress: revealProgress)
106
+ }
107
+ ```
108
+
109
+ This keeps the motion coherent and avoids duplicate-hit-target bugs.
110
+
111
+ ## Haptics and affordances
112
+
113
+ - Use light threshold haptics when the reveal begins and stronger haptics near the committed state.
114
+ - Keep a visible affordance like a chevron or pill while `progress` is near zero.
115
+ - Flip, fade, or blur the affordance as the secondary section becomes active.
116
+
117
+ ## Interaction guards
118
+
119
+ - Disable vertical scrolling when a conflicting mode is active, such as pinch-to-zoom, crop, or full-screen media manipulation.
120
+ - Disable hit testing on overlays that should disappear once the secondary content is revealed.
121
+ - Avoid same-axis nested scroll views unless the inner view is effectively static or disabled during the reveal.
122
+
123
+ ## Pitfalls
124
+
125
+ - Do not hard-code the progress divisor. Measure the secondary section height or another real reveal distance.
126
+ - Do not mix multiple animation sources for the same property. If `progress` drives it, keep other animations off that property.
127
+ - Do not store derived state like `isSecondaryVisible` unless another API requires it. Prefer deriving it from `progress` or visible scroll targets.
128
+ - Beware of layout feedback loops when measuring heights. Clamp zero values and update only when the measured height actually changes.
129
+
130
+ ## Concrete example
131
+
132
+ - Pool iOS tile detail reveal: `/Users/dimillian/Documents/Dev/Pool/pool-ios/Pool/Sources/Features/Tile/Detail/TileDetailView.swift`
133
+ - Secondary content anchor example: `/Users/dimillian/Documents/Dev/Pool/pool-ios/Pool/Sources/Features/Tile/Detail/TileDetailIntentListView.swift`
@@ -0,0 +1,87 @@
1
+ # ScrollView and Lazy stacks
2
+
3
+ ## Intent
4
+
5
+ Use `ScrollView` with `LazyVStack`, `LazyHStack`, or `LazyVGrid` when you need custom layout, mixed content, or horizontal/ grid-based scrolling.
6
+
7
+ ## Core patterns
8
+
9
+ - Prefer `ScrollView` + `LazyVStack` for chat-like or custom feed layouts.
10
+ - Use `ScrollView(.horizontal)` + `LazyHStack` for chips, tags, avatars, and media strips.
11
+ - Use `LazyVGrid` for icon/media grids; prefer adaptive columns when possible.
12
+ - Use `ScrollViewReader` for scroll-to-top/bottom and anchor-based jumps.
13
+ - Use `safeAreaInset(edge:)` for input bars that should stick above the keyboard.
14
+
15
+ ## Example: vertical custom feed
16
+
17
+ ```swift
18
+ @MainActor
19
+ struct ConversationView: View {
20
+ private enum Constants { static let bottomAnchor = "bottom" }
21
+ @State private var scrollProxy: ScrollViewProxy?
22
+
23
+ var body: some View {
24
+ ScrollViewReader { proxy in
25
+ ScrollView {
26
+ LazyVStack {
27
+ ForEach(messages) { message in
28
+ MessageRow(message: message)
29
+ .id(message.id)
30
+ }
31
+ Color.clear.frame(height: 1).id(Constants.bottomAnchor)
32
+ }
33
+ .padding(.horizontal, .layoutPadding)
34
+ }
35
+ .safeAreaInset(edge: .bottom) {
36
+ MessageInputBar()
37
+ }
38
+ .onAppear {
39
+ scrollProxy = proxy
40
+ withAnimation {
41
+ proxy.scrollTo(Constants.bottomAnchor, anchor: .bottom)
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ ## Example: horizontal chips
50
+
51
+ ```swift
52
+ ScrollView(.horizontal, showsIndicators: false) {
53
+ LazyHStack(spacing: 8) {
54
+ ForEach(chips) { chip in
55
+ ChipView(chip: chip)
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ ## Example: adaptive grid
62
+
63
+ ```swift
64
+ let columns = [GridItem(.adaptive(minimum: 120))]
65
+
66
+ ScrollView {
67
+ LazyVGrid(columns: columns, spacing: 8) {
68
+ ForEach(items) { item in
69
+ GridItemView(item: item)
70
+ }
71
+ }
72
+ .padding(8)
73
+ }
74
+ ```
75
+
76
+ ## Design choices to keep
77
+
78
+ - Use `Lazy*` stacks when item counts are large or unknown.
79
+ - Use non-lazy stacks for small, fixed-size content to avoid lazy overhead.
80
+ - Keep IDs stable when using `ScrollViewReader`.
81
+ - Prefer explicit animations (`withAnimation`) when scrolling to an ID.
82
+
83
+ ## Pitfalls
84
+
85
+ - Avoid nesting scroll views of the same axis; it causes gesture conflicts.
86
+ - Don’t combine `List` and `ScrollView` in the same hierarchy without a clear reason.
87
+ - Overuse of `LazyVStack` for tiny content can add unnecessary complexity.
@@ -0,0 +1,71 @@
1
+ # Searchable
2
+
3
+ ## Intent
4
+
5
+ Use `searchable` to add native search UI with optional scopes and async results.
6
+
7
+ ## Core patterns
8
+
9
+ - Bind `searchable(text:)` to local state.
10
+ - Use `.searchScopes` for multiple search modes.
11
+ - Use `.task(id: searchQuery)` or debounced tasks to avoid overfetching.
12
+ - Show placeholders or progress states while results load.
13
+
14
+ ## Example: searchable with scopes
15
+
16
+ ```swift
17
+ @MainActor
18
+ struct ExploreView: View {
19
+ @State private var searchQuery = ""
20
+ @State private var searchScope: SearchScope = .all
21
+ @State private var isSearching = false
22
+ @State private var results: [SearchResult] = []
23
+
24
+ var body: some View {
25
+ List {
26
+ if isSearching {
27
+ ProgressView()
28
+ } else {
29
+ ForEach(results) { result in
30
+ SearchRow(result: result)
31
+ }
32
+ }
33
+ }
34
+ .searchable(
35
+ text: $searchQuery,
36
+ placement: .navigationBarDrawer(displayMode: .always),
37
+ prompt: Text("Search")
38
+ )
39
+ .searchScopes($searchScope) {
40
+ ForEach(SearchScope.allCases, id: \.self) { scope in
41
+ Text(scope.title)
42
+ }
43
+ }
44
+ .task(id: searchQuery) {
45
+ await runSearch()
46
+ }
47
+ }
48
+
49
+ private func runSearch() async {
50
+ guard !searchQuery.isEmpty else {
51
+ results = []
52
+ return
53
+ }
54
+ isSearching = true
55
+ defer { isSearching = false }
56
+ try? await Task.sleep(for: .milliseconds(250))
57
+ results = await fetchResults(query: searchQuery, scope: searchScope)
58
+ }
59
+ }
60
+ ```
61
+
62
+ ## Design choices to keep
63
+
64
+ - Show a placeholder when search is empty or has no results.
65
+ - Debounce input to avoid spamming the network.
66
+ - Keep search state local to the view.
67
+
68
+ ## Pitfalls
69
+
70
+ - Avoid running searches for empty strings.
71
+ - Don’t block the main thread during fetch.
@@ -0,0 +1,155 @@
1
+ # Sheets
2
+
3
+ ## Intent
4
+
5
+ Use a centralized sheet routing pattern so any view can present modals without prop-drilling. This keeps sheet state in one place and scales as the app grows.
6
+
7
+ ## Core architecture
8
+
9
+ - Define a `SheetDestination` enum that describes every modal and is `Identifiable`.
10
+ - Store the current sheet in a router object (`presentedSheet: SheetDestination?`).
11
+ - Create a view modifier like `withSheetDestinations(...)` that maps the enum to concrete sheet views.
12
+ - Inject the router into the environment so child views can set `presentedSheet` directly.
13
+
14
+ ## Example: item-driven local sheet
15
+
16
+ Use this when sheet state is local to one screen and does not need centralized routing.
17
+
18
+ ```swift
19
+ @State private var selectedItem: Item?
20
+
21
+ .sheet(item: $selectedItem) { item in
22
+ EditItemSheet(item: item)
23
+ }
24
+ ```
25
+
26
+ ## Example: SheetDestination enum
27
+
28
+ ```swift
29
+ enum SheetDestination: Identifiable, Hashable {
30
+ case composer
31
+ case editProfile
32
+ case settings
33
+ case report(itemID: String)
34
+
35
+ var id: String {
36
+ switch self {
37
+ case .composer, .editProfile:
38
+ // Use the same id to ensure only one editor-like sheet is active at a time.
39
+ return "editor"
40
+ case .settings:
41
+ return "settings"
42
+ case .report:
43
+ return "report"
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ ## Example: withSheetDestinations modifier
50
+
51
+ ```swift
52
+ extension View {
53
+ func withSheetDestinations(
54
+ sheet: Binding<SheetDestination?>
55
+ ) -> some View {
56
+ sheet(item: sheet) { destination in
57
+ Group {
58
+ switch destination {
59
+ case .composer:
60
+ ComposerView()
61
+ case .editProfile:
62
+ EditProfileView()
63
+ case .settings:
64
+ SettingsView()
65
+ case .report(let itemID):
66
+ ReportView(itemID: itemID)
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
72
+ ```
73
+
74
+ ## Example: presenting from a child view
75
+
76
+ ```swift
77
+ struct StatusRow: View {
78
+ @Environment(RouterPath.self) private var router
79
+
80
+ var body: some View {
81
+ Button("Report") {
82
+ router.presentedSheet = .report(itemID: "123")
83
+ }
84
+ }
85
+ }
86
+ ```
87
+
88
+ ## Required wiring
89
+
90
+ For the child view to work, a parent view must:
91
+ - own the router instance,
92
+ - attach `withSheetDestinations(sheet: $router.presentedSheet)` (or an equivalent `sheet(item:)` handler), and
93
+ - inject it with `.environment(router)` after the sheet modifier so the modal content inherits it.
94
+
95
+ This makes the child assignment to `router.presentedSheet` drive presentation at the root.
96
+
97
+ ## Example: sheets that need their own navigation
98
+
99
+ Wrap sheet content in a `NavigationStack` so it can push within the modal.
100
+
101
+ ```swift
102
+ struct NavigationSheet<Content: View>: View {
103
+ var content: () -> Content
104
+
105
+ var body: some View {
106
+ NavigationStack {
107
+ content()
108
+ .toolbar { CloseToolbarItem() }
109
+ }
110
+ }
111
+ }
112
+ ```
113
+
114
+ ## Example: sheet owns its actions
115
+
116
+ Keep dismissal and confirmation logic inside the sheet when the actions belong to the modal itself.
117
+
118
+ ```swift
119
+ struct EditItemSheet: View {
120
+ @Environment(\.dismiss) private var dismiss
121
+ @Environment(Store.self) private var store
122
+
123
+ let item: Item
124
+ @State private var isSaving = false
125
+
126
+ var body: some View {
127
+ VStack {
128
+ Button(isSaving ? "Saving..." : "Save") {
129
+ Task { await save() }
130
+ }
131
+ }
132
+ }
133
+
134
+ private func save() async {
135
+ isSaving = true
136
+ await store.save(item)
137
+ dismiss()
138
+ }
139
+ }
140
+ ```
141
+
142
+ ## Design choices to keep
143
+
144
+ - Centralize sheet routing so features can present modals without wiring bindings through many layers.
145
+ - Use `sheet(item:)` to guarantee a single sheet is active and to drive presentation from the enum.
146
+ - Group related sheets under the same `id` when they are mutually exclusive (e.g., editor flows).
147
+ - Keep sheet views lightweight and composed from smaller views; avoid large monoliths.
148
+ - Let sheets own their actions and call `dismiss()` internally instead of forwarding `onCancel` or `onConfirm` closures through many layers.
149
+
150
+ ## Pitfalls
151
+
152
+ - Avoid mixing `sheet(isPresented:)` and `sheet(item:)` for the same concern; prefer a single enum.
153
+ - Avoid `if let` inside a sheet body when the presentation state already carries the selected model; prefer `sheet(item:)`.
154
+ - Do not store heavy state inside `SheetDestination`; pass lightweight identifiers or models.
155
+ - If multiple sheets can appear from the same screen, give them distinct `id` values.
@@ -0,0 +1,72 @@
1
+ # Split views and columns
2
+
3
+ ## Intent
4
+
5
+ Provide a lightweight, customizable multi-column layout for iPad/macOS without relying on `NavigationSplitView`.
6
+
7
+ ## Custom split column pattern (manual HStack)
8
+
9
+ Use this when you want full control over column sizing, behavior, and environment tweaks.
10
+
11
+ ```swift
12
+ @MainActor
13
+ struct AppView: View {
14
+ @Environment(\.horizontalSizeClass) private var horizontalSizeClass
15
+ @AppStorage("showSecondaryColumn") private var showSecondaryColumn = true
16
+
17
+ var body: some View {
18
+ HStack(spacing: 0) {
19
+ primaryColumn
20
+ if shouldShowSecondaryColumn {
21
+ Divider().edgesIgnoringSafeArea(.all)
22
+ secondaryColumn
23
+ }
24
+ }
25
+ }
26
+
27
+ private var shouldShowSecondaryColumn: Bool {
28
+ horizontalSizeClass == .regular
29
+ && showSecondaryColumn
30
+ }
31
+
32
+ private var primaryColumn: some View {
33
+ TabView { /* tabs */ }
34
+ }
35
+
36
+ private var secondaryColumn: some View {
37
+ NotificationsTab()
38
+ .environment(\.isSecondaryColumn, true)
39
+ .frame(maxWidth: .secondaryColumnWidth)
40
+ }
41
+ }
42
+ ```
43
+
44
+ ## Notes on the custom approach
45
+
46
+ - Use a shared preference or setting to toggle the secondary column.
47
+ - Inject an environment flag (e.g., `isSecondaryColumn`) so child views can adapt behavior.
48
+ - Prefer a fixed or capped width for the secondary column to avoid layout thrash.
49
+
50
+ ## Alternative: NavigationSplitView
51
+
52
+ `NavigationSplitView` can handle sidebar + detail + supplementary columns for you, but is harder to customize in cases like:\n- a dedicated notification column independent of selection,\n- custom sizing, or\n- different toolbar behaviors per column.
53
+
54
+ ```swift
55
+ @MainActor
56
+ struct AppView: View {
57
+ var body: some View {
58
+ NavigationSplitView {
59
+ SidebarView()
60
+ } content: {
61
+ MainContentView()
62
+ } detail: {
63
+ NotificationsView()
64
+ }
65
+ }
66
+ }
67
+ ```
68
+
69
+ ## When to choose which
70
+
71
+ - Use the manual HStack split when you need full control or a non-standard secondary column.
72
+ - Use `NavigationSplitView` when you want a standard system layout with minimal customization.