swift-code-reviewer-skill 1.1.1 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CHANGELOG.md +44 -162
  2. package/README.md +91 -21
  3. package/SKILL.md +107 -725
  4. package/bin/install.js +87 -22
  5. package/package.json +16 -2
  6. package/references/companion-skills.md +70 -0
  7. package/skills/README.md +43 -0
  8. package/skills/swift-concurrency/NOTICE.md +18 -0
  9. package/skills/swift-concurrency/SKILL.md +235 -0
  10. package/skills/swift-concurrency/references/actors.md +640 -0
  11. package/skills/swift-concurrency/references/async-await-basics.md +249 -0
  12. package/skills/swift-concurrency/references/async-sequences.md +635 -0
  13. package/skills/swift-concurrency/references/core-data.md +533 -0
  14. package/skills/swift-concurrency/references/glossary.md +96 -0
  15. package/skills/swift-concurrency/references/linting.md +38 -0
  16. package/skills/swift-concurrency/references/memory-management.md +542 -0
  17. package/skills/swift-concurrency/references/migration.md +721 -0
  18. package/skills/swift-concurrency/references/performance.md +574 -0
  19. package/skills/swift-concurrency/references/sendable.md +578 -0
  20. package/skills/swift-concurrency/references/tasks.md +604 -0
  21. package/skills/swift-concurrency/references/testing.md +565 -0
  22. package/skills/swift-concurrency/references/threading.md +452 -0
  23. package/skills/swift-expert/NOTICE.md +18 -0
  24. package/skills/swift-expert/SKILL.md +226 -0
  25. package/skills/swift-expert/references/async-concurrency.md +363 -0
  26. package/skills/swift-expert/references/memory-performance.md +380 -0
  27. package/skills/swift-expert/references/protocol-oriented.md +357 -0
  28. package/skills/swift-expert/references/swiftui-patterns.md +294 -0
  29. package/skills/swift-expert/references/testing-patterns.md +402 -0
  30. package/skills/swift-testing/NOTICE.md +18 -0
  31. package/skills/swift-testing/SKILL.md +295 -0
  32. package/skills/swift-testing/references/async-testing.md +245 -0
  33. package/skills/swift-testing/references/dump-snapshot-testing.md +265 -0
  34. package/skills/swift-testing/references/fixtures.md +193 -0
  35. package/skills/swift-testing/references/integration-testing.md +189 -0
  36. package/skills/swift-testing/references/migration-xctest.md +301 -0
  37. package/skills/swift-testing/references/parameterized-tests.md +171 -0
  38. package/skills/swift-testing/references/snapshot-testing.md +201 -0
  39. package/skills/swift-testing/references/test-doubles.md +243 -0
  40. package/skills/swift-testing/references/test-organization.md +231 -0
  41. package/skills/swiftui-expert-skill/NOTICE.md +18 -0
  42. package/skills/swiftui-expert-skill/SKILL.md +281 -0
  43. package/skills/swiftui-expert-skill/references/accessibility-patterns.md +151 -0
  44. package/skills/swiftui-expert-skill/references/animation-advanced.md +403 -0
  45. package/skills/swiftui-expert-skill/references/animation-basics.md +284 -0
  46. package/skills/swiftui-expert-skill/references/animation-transitions.md +326 -0
  47. package/skills/swiftui-expert-skill/references/charts-accessibility.md +135 -0
  48. package/skills/swiftui-expert-skill/references/charts.md +602 -0
  49. package/skills/swiftui-expert-skill/references/image-optimization.md +203 -0
  50. package/skills/swiftui-expert-skill/references/latest-apis.md +464 -0
  51. package/skills/swiftui-expert-skill/references/layout-best-practices.md +266 -0
  52. package/skills/swiftui-expert-skill/references/liquid-glass.md +414 -0
  53. package/skills/swiftui-expert-skill/references/list-patterns.md +394 -0
  54. package/skills/swiftui-expert-skill/references/macos-scenes.md +318 -0
  55. package/skills/swiftui-expert-skill/references/macos-views.md +357 -0
  56. package/skills/swiftui-expert-skill/references/macos-window-styling.md +303 -0
  57. package/skills/swiftui-expert-skill/references/performance-patterns.md +403 -0
  58. package/skills/swiftui-expert-skill/references/scroll-patterns.md +293 -0
  59. package/skills/swiftui-expert-skill/references/sheet-navigation-patterns.md +363 -0
  60. package/skills/swiftui-expert-skill/references/state-management.md +417 -0
  61. package/skills/swiftui-expert-skill/references/view-structure.md +389 -0
  62. package/skills/swiftui-ui-patterns/NOTICE.md +18 -0
  63. package/skills/swiftui-ui-patterns/SKILL.md +95 -0
  64. package/skills/swiftui-ui-patterns/references/app-wiring.md +201 -0
  65. package/skills/swiftui-ui-patterns/references/async-state.md +96 -0
  66. package/skills/swiftui-ui-patterns/references/components-index.md +50 -0
  67. package/skills/swiftui-ui-patterns/references/controls.md +57 -0
  68. package/skills/swiftui-ui-patterns/references/deeplinks.md +66 -0
  69. package/skills/swiftui-ui-patterns/references/focus.md +90 -0
  70. package/skills/swiftui-ui-patterns/references/form.md +97 -0
  71. package/skills/swiftui-ui-patterns/references/grids.md +71 -0
  72. package/skills/swiftui-ui-patterns/references/haptics.md +71 -0
  73. package/skills/swiftui-ui-patterns/references/input-toolbar.md +51 -0
  74. package/skills/swiftui-ui-patterns/references/lightweight-clients.md +93 -0
  75. package/skills/swiftui-ui-patterns/references/list.md +86 -0
  76. package/skills/swiftui-ui-patterns/references/loading-placeholders.md +38 -0
  77. package/skills/swiftui-ui-patterns/references/macos-settings.md +71 -0
  78. package/skills/swiftui-ui-patterns/references/matched-transitions.md +59 -0
  79. package/skills/swiftui-ui-patterns/references/media.md +73 -0
  80. package/skills/swiftui-ui-patterns/references/menu-bar.md +101 -0
  81. package/skills/swiftui-ui-patterns/references/navigationstack.md +159 -0
  82. package/skills/swiftui-ui-patterns/references/overlay.md +45 -0
  83. package/skills/swiftui-ui-patterns/references/performance.md +62 -0
  84. package/skills/swiftui-ui-patterns/references/previews.md +48 -0
  85. package/skills/swiftui-ui-patterns/references/scroll-reveal.md +133 -0
  86. package/skills/swiftui-ui-patterns/references/scrollview.md +87 -0
  87. package/skills/swiftui-ui-patterns/references/searchable.md +71 -0
  88. package/skills/swiftui-ui-patterns/references/sheets.md +155 -0
  89. package/skills/swiftui-ui-patterns/references/split-views.md +72 -0
  90. package/skills/swiftui-ui-patterns/references/tabview.md +114 -0
  91. package/skills/swiftui-ui-patterns/references/theming.md +71 -0
  92. package/skills/swiftui-ui-patterns/references/title-menus.md +93 -0
  93. package/skills/swiftui-ui-patterns/references/top-bar.md +49 -0
  94. package/templates/agents/swift-code-reviewer.md +78 -0
  95. package/templates/commands/review.md +56 -0
@@ -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.
@@ -0,0 +1,114 @@
1
+ # TabView
2
+
3
+ ## Intent
4
+
5
+ Use this pattern for a scalable, multi-platform tab architecture with:
6
+ - a single source of truth for tab identity and content,
7
+ - platform-specific tab sets and sidebar sections,
8
+ - dynamic tabs sourced from data,
9
+ - an interception hook for special tabs (e.g., compose).
10
+
11
+ ## Core architecture
12
+
13
+ - `AppTab` enum defines identity, labels, icons, and content builder.
14
+ - `SidebarSections` enum groups tabs for sidebar sections.
15
+ - `AppView` owns the `TabView` and selection binding, and routes tab changes through `updateTab`.
16
+
17
+ ## Example: custom binding with side effects
18
+
19
+ Use this when tab selection needs side effects, like intercepting a special tab to perform an action instead of changing selection.
20
+
21
+ ```swift
22
+ @MainActor
23
+ struct AppView: View {
24
+ @Binding var selectedTab: AppTab
25
+
26
+ var body: some View {
27
+ TabView(selection: .init(
28
+ get: { selectedTab },
29
+ set: { updateTab(with: $0) }
30
+ )) {
31
+ ForEach(availableSections) { section in
32
+ TabSection(section.title) {
33
+ ForEach(section.tabs) { tab in
34
+ Tab(value: tab) {
35
+ tab.makeContentView(
36
+ homeTimeline: $timeline,
37
+ selectedTab: $selectedTab,
38
+ pinnedFilters: $pinnedFilters
39
+ )
40
+ } label: {
41
+ tab.label
42
+ }
43
+ .tabPlacement(tab.tabPlacement)
44
+ }
45
+ }
46
+ .tabPlacement(.sidebarOnly)
47
+ }
48
+ }
49
+ }
50
+
51
+ private func updateTab(with newTab: AppTab) {
52
+ if newTab == .post {
53
+ // Intercept special tabs (compose) instead of changing selection.
54
+ presentComposer()
55
+ return
56
+ }
57
+ selectedTab = newTab
58
+ }
59
+ }
60
+ ```
61
+
62
+ ## Example: direct binding without side effects
63
+
64
+ Use this when selection is purely state-driven.
65
+
66
+ ```swift
67
+ @MainActor
68
+ struct AppView: View {
69
+ @Binding var selectedTab: AppTab
70
+
71
+ var body: some View {
72
+ TabView(selection: $selectedTab) {
73
+ ForEach(availableSections) { section in
74
+ TabSection(section.title) {
75
+ ForEach(section.tabs) { tab in
76
+ Tab(value: tab) {
77
+ tab.makeContentView(
78
+ homeTimeline: $timeline,
79
+ selectedTab: $selectedTab,
80
+ pinnedFilters: $pinnedFilters
81
+ )
82
+ } label: {
83
+ tab.label
84
+ }
85
+ .tabPlacement(tab.tabPlacement)
86
+ }
87
+ }
88
+ .tabPlacement(.sidebarOnly)
89
+ }
90
+ }
91
+ }
92
+ }
93
+ ```
94
+
95
+ ## Design choices to keep
96
+
97
+ - Centralize tab identity and content in `AppTab` with `makeContentView(...)`.
98
+ - Use `Tab(value:)` with `selection` binding for state-driven tab selection.
99
+ - Route selection changes through `updateTab` to handle special tabs and scroll-to-top behavior.
100
+ - Use `TabSection` + `.tabPlacement(.sidebarOnly)` for sidebar structure.
101
+ - Use `.tabPlacement(.pinned)` in `AppTab.tabPlacement` for a single pinned tab; this is commonly used for iOS 26 `.searchable` tab content, but can be used for any tab.
102
+
103
+ ## Dynamic tabs pattern
104
+
105
+ - `SidebarSections` handles dynamic data tabs.
106
+ - `AppTab.anyTimelineFilter(filter:)` wraps dynamic tabs in a single enum case.
107
+ - The enum provides label/icon/title for dynamic tabs via the filter type.
108
+
109
+ ## Pitfalls
110
+
111
+ - Avoid adding ViewModels for tabs; keep state local or in `@Observable` services.
112
+ - Do not nest `@Observable` objects inside other `@Observable` objects.
113
+ - Ensure `AppTab.id` values are stable; dynamic cases should hash on stable IDs.
114
+ - Special tabs (compose) should not change selection.
@@ -0,0 +1,71 @@
1
+ # Theming and dynamic type
2
+
3
+ ## Intent
4
+
5
+ Provide a clean, scalable theming approach that keeps view code semantic and consistent.
6
+
7
+ ## Core patterns
8
+
9
+ - Use a single `Theme` object as the source of truth (colors, fonts, spacing).
10
+ - Inject theme at the app root and read it via `@Environment(Theme.self)` in views.
11
+ - Prefer semantic colors (`primaryBackground`, `secondaryBackground`, `label`, `tint`) instead of raw colors.
12
+ - Keep user-facing theme controls in a dedicated settings screen.
13
+ - Apply Dynamic Type scaling through custom fonts or `.font(.scaled...)`.
14
+
15
+ ## Example: Theme object
16
+
17
+ ```swift
18
+ @MainActor
19
+ @Observable
20
+ final class Theme {
21
+ var tintColor: Color = .blue
22
+ var primaryBackground: Color = .white
23
+ var secondaryBackground: Color = .gray.opacity(0.1)
24
+ var labelColor: Color = .primary
25
+ var fontSizeScale: Double = 1.0
26
+ }
27
+ ```
28
+
29
+ ## Example: inject at app root
30
+
31
+ ```swift
32
+ @main
33
+ struct MyApp: App {
34
+ @State private var theme = Theme()
35
+
36
+ var body: some Scene {
37
+ WindowGroup {
38
+ AppView()
39
+ .environment(theme)
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ ## Example: view usage
46
+
47
+ ```swift
48
+ struct ProfileView: View {
49
+ @Environment(Theme.self) private var theme
50
+
51
+ var body: some View {
52
+ VStack {
53
+ Text("Profile")
54
+ .foregroundStyle(theme.labelColor)
55
+ }
56
+ .background(theme.primaryBackground)
57
+ }
58
+ }
59
+ ```
60
+
61
+ ## Design choices to keep
62
+
63
+ - Keep theme values semantic and minimal; avoid duplicating system colors.
64
+ - Store user-selected theme values in persistent storage if needed.
65
+ - Ensure contrast between text and backgrounds.
66
+
67
+ ## Pitfalls
68
+
69
+ - Avoid sprinkling raw `Color` values in views; it breaks consistency.
70
+ - Do not tie theme to a single view’s local state.
71
+ - Avoid using `@Environment(\\.colorScheme)` as the only theme control; it should complement your theme.
@@ -0,0 +1,93 @@
1
+ # Title menus
2
+
3
+ ## Intent
4
+
5
+ Use a title menu in the navigation bar to provide context‑specific filtering or quick actions without adding extra chrome.
6
+
7
+ ## Core patterns
8
+
9
+ - Use `ToolbarTitleMenu` to attach a menu to the navigation title.
10
+ - Keep the menu content compact and grouped with dividers.
11
+
12
+ ## Example: title menu for filters
13
+
14
+ ```swift
15
+ @ToolbarContentBuilder
16
+ private var toolbarView: some ToolbarContent {
17
+ ToolbarTitleMenu {
18
+ Button("Latest") { timeline = .latest }
19
+ Button("Resume") { timeline = .resume }
20
+ Divider()
21
+ Button("Local") { timeline = .local }
22
+ Button("Federated") { timeline = .federated }
23
+ }
24
+ }
25
+ ```
26
+
27
+ ## Example: attach to a view
28
+
29
+ ```swift
30
+ NavigationStack {
31
+ TimelineView()
32
+ .toolbar {
33
+ toolbarView
34
+ }
35
+ }
36
+ ```
37
+
38
+ ## Example: title + menu together
39
+
40
+ ```swift
41
+ struct TimelineScreen: View {
42
+ @State private var timeline: TimelineFilter = .home
43
+
44
+ var body: some View {
45
+ NavigationStack {
46
+ TimelineView()
47
+ .toolbar {
48
+ ToolbarItem(placement: .principal) {
49
+ VStack(spacing: 2) {
50
+ Text(timeline.title)
51
+ .font(.headline)
52
+ Text(timeline.subtitle)
53
+ .font(.caption)
54
+ .foregroundStyle(.secondary)
55
+ }
56
+ }
57
+
58
+ ToolbarTitleMenu {
59
+ Button("Home") { timeline = .home }
60
+ Button("Local") { timeline = .local }
61
+ Button("Federated") { timeline = .federated }
62
+ }
63
+ }
64
+ .navigationBarTitleDisplayMode(.inline)
65
+ }
66
+ }
67
+ }
68
+ ```
69
+
70
+ ## Example: title + subtitle with menu
71
+
72
+ ```swift
73
+ ToolbarItem(placement: .principal) {
74
+ VStack(spacing: 2) {
75
+ Text(title)
76
+ .font(.headline)
77
+ Text(subtitle)
78
+ .font(.caption)
79
+ .foregroundStyle(.secondary)
80
+ }
81
+ }
82
+ ```
83
+
84
+ ## Design choices to keep
85
+
86
+ - Only show the title menu when filtering or context switching is available.
87
+ - Keep the title readable; avoid long labels that truncate.
88
+ - Use secondary text below the title if extra context is needed.
89
+
90
+ ## Pitfalls
91
+
92
+ - Don’t overload the menu with too many options.
93
+ - Avoid using title menus for destructive actions.
@@ -0,0 +1,49 @@
1
+ # Top bar overlays (iOS 26+ and fallback)
2
+
3
+ ## Intent
4
+
5
+ Provide a custom top selector or pill row that sits above scroll content, using `safeAreaBar(.top)` on iOS 26 and a compatible fallback on earlier OS versions.
6
+
7
+ ## iOS 26+ approach
8
+
9
+ Use `safeAreaBar(edge: .top)` to attach the view to the safe area bar.
10
+
11
+ ```swift
12
+ if #available(iOS 26.0, *) {
13
+ content
14
+ .safeAreaBar(edge: .top) {
15
+ TopSelectorView()
16
+ .padding(.horizontal, .layoutPadding)
17
+ }
18
+ }
19
+ ```
20
+
21
+ ## Fallback for earlier iOS
22
+
23
+ Use `.safeAreaInset(edge: .top)` and hide the toolbar background to avoid double layers.
24
+
25
+ ```swift
26
+ content
27
+ .toolbarBackground(.hidden, for: .navigationBar)
28
+ .safeAreaInset(edge: .top, spacing: 0) {
29
+ VStack(spacing: 0) {
30
+ TopSelectorView()
31
+ .padding(.vertical, 8)
32
+ .padding(.horizontal, .layoutPadding)
33
+ .background(Color.primary.opacity(0.06))
34
+ .background(Material.ultraThin)
35
+ Divider()
36
+ }
37
+ }
38
+ ```
39
+
40
+ ## Design choices to keep
41
+
42
+ - Use `safeAreaBar` when available; it integrates better with the navigation bar.
43
+ - Use a subtle background + divider in the fallback to keep separation from content.
44
+ - Keep the selector height compact to avoid pushing content too far down.
45
+
46
+ ## Pitfalls
47
+
48
+ - Don’t stack multiple top insets; it can create extra padding.
49
+ - Avoid heavy, opaque backgrounds that fight the navigation bar.
@@ -0,0 +1,78 @@
1
+ # Swift Code Review Agent
2
+
3
+ You are a senior Swift/SwiftUI code reviewer. Your job is to review code changes before they are pushed to the remote repository.
4
+
5
+ ## Skills
6
+
7
+ Load and follow the rules from `~/.claude/skills/swift-code-reviewer-skill/SKILL.md` and all files in its `references/` directory.
8
+
9
+ ## Workflow
10
+
11
+ When invoked, execute these steps in order:
12
+
13
+ ### 1. Collect the diff
14
+
15
+ ```bash
16
+ git diff --staged -- '*.swift'
17
+ ```
18
+
19
+ If nothing is staged, fall back to:
20
+
21
+ ```bash
22
+ git diff HEAD -- '*.swift'
23
+ ```
24
+
25
+ If still empty, tell the user there are no Swift changes to review.
26
+
27
+ ### 2. Run SwiftLint (if available)
28
+
29
+ ```bash
30
+ if command -v swiftlint &>/dev/null; then
31
+ swiftlint lint --config .swiftlint.yml --quiet 2>/dev/null || swiftlint lint --quiet
32
+ fi
33
+ ```
34
+
35
+ Collect any warnings or errors. If SwiftLint is not installed, skip and note it.
36
+
37
+ ### 3. Review
38
+
39
+ Analyze the diff using the swift-code-reviewer-skill rules. Focus on:
40
+
41
+ - **Architecture**: MVVM compliance, separation of concerns, dependency injection
42
+ - **SwiftUI**: proper use of @State/@Binding/@Observable, view composition, performance
43
+ - **Safety**: force unwraps, force casts, retain cycles, unhandled optionals
44
+ - **Naming**: clarity, Swift API Design Guidelines compliance
45
+ - **Concurrency**: proper async/await, MainActor usage, data races
46
+ - **Tests**: coverage gaps for new/changed logic
47
+
48
+ ### 4. Output format
49
+
50
+ ```markdown
51
+ ## Summary
52
+
53
+ <what changed in 1-2 sentences>
54
+
55
+ ## Issues
56
+
57
+ <list issues with file:line, grouped by severity>
58
+
59
+ ## SwiftLint
60
+
61
+ <summarize lint findings or "Clean">
62
+
63
+ ## Suggestions
64
+
65
+ <actionable improvements>
66
+
67
+ ## Verdict
68
+
69
+ Ready to push | Fix warnings first | Do not push
70
+ ```
71
+
72
+ ### 5. Rules
73
+
74
+ - Be direct. No filler, no praise for basic competence.
75
+ - Every issue must include the file and line number.
76
+ - If the diff is clean, say so — don't invent problems.
77
+ - Prioritize issues that would break production or cause bugs.
78
+ - Ignore generated files, Pods, and third-party code.
@@ -0,0 +1,56 @@
1
+ # /review — Swift Code Review
2
+
3
+ Use the agent defined in .claude/agents/swift-code-reviewer.md as your primary review rules. Combine its skill-based analysis with the checklist below.
4
+
5
+ Run the full code review checklist against current Swift changes.
6
+
7
+ ## Behavior
8
+
9
+ When invoked:
10
+
11
+ 1. **Identify changed files** — use `git diff --name-only` (staged + unstaged), filter to `*.swift`.
12
+ 2. **Load CLAUDE.md** — read project conventions if `.claude/CLAUDE.md` exists.
13
+ 3. **Run SwiftLint** — if available, collect warnings/errors.
14
+ 4. **Run the universal checklist** against the diff.
15
+ 5. **Run Swift-specific checks** using the swift-code-reviewer-skill rules.
16
+ 6. **Run CLAUDE.md-specific checks** — any custom rules defined in the project.
17
+ 7. **Report findings** using the signal system.
18
+
19
+ ## Output Format
20
+
21
+ ```
22
+ Code Review — [N files changed]
23
+
24
+ Universal:
25
+ Pass Naming: consistent with project conventions
26
+ Pass Error handling: all errors handled
27
+ Issue Edge case: `processItems` doesn't handle empty array
28
+ Suggestion Complexity: `calculateTotal` could extract tax logic
29
+
30
+ Swift/SwiftUI:
31
+ Pass No force unwraps
32
+ Issue Retain cycle: closure in `fetchData` captures self strongly
33
+ Pass Accessibility labels present
34
+
35
+ CLAUDE.md:
36
+ Convention Line 23: uses `if let` but CLAUDE.md requires `guard let` for early returns
37
+ Pass Architecture: follows MVVM pattern
38
+
39
+ Result: 2 issues to fix, 1 suggestion
40
+ ```
41
+
42
+ ## Signal Words
43
+
44
+ | Signal | Meaning |
45
+ | -------------- | ---------------------------------- |
46
+ | **Pass** | Item satisfied |
47
+ | **Suggestion** | Optional improvement, non-blocking |
48
+ | **Issue** | Must be fixed before commit |
49
+ | **Convention** | Violation from CLAUDE.md |
50
+
51
+ ## Important
52
+
53
+ - Only flag items relevant to actual changes, not the entire codebase
54
+ - One line per item — be thorough but concise
55
+ - After listing issues, offer to help fix them
56
+ - If no issues found, confirm with a clean summary