.claude/skills/tooling/swiftui/SKILL.md
macOS Tahoe (26.0+) SwiftUI conventions and project layout standards. Not a tutorial — covers Tahoe-specific patterns and project deviations only.
npx skillsauth add brdohman/agile-maestro swiftui-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
// View owns ViewModel
@State private var viewModel = ItemListViewModel()
// Shared dependency via @Environment
@Environment(SessionViewModel.self) private var session
Never use @StateObject, @ObservedObject, or @EnvironmentObject.
Use .glassEffect() for floating panels and overlay cards. Prefer .regularMaterial for sidebars and .ultraThinMaterial for popovers.
// Floating inspector panel
VStack { ... }
.glassEffect()
.padding(16)
NavigationSplitView {
SidebarView()
.navigationSplitViewColumnWidth(min: 200, ideal: 240, max: 300)
} detail: {
DetailView()
}
.navigationSplitViewStyle(.balanced)
.toolbar {
ToolbarItem(placement: .navigation) { /* back/breadcrumb */ }
ToolbarItem(placement: .primaryAction) { /* main CTA */ }
ToolbarItemGroup(placement: .automatic) { /* secondary actions */ }
}
.windowToolbarStyle(.unified)
Animation in NSHostingView: Use explicit timing; .animation(.default) may not fire.
.animation(.easeInOut(duration: 0.2), value: isExpanded)
Transparency: Test .background(Color.white.opacity(0.5)) on device — system materials are safer.
VStack layout: Wrap in .fixedSize(horizontal: false, vertical: true) when height is unpredictable inside split views.
WindowGroup {
ContentView()
}
.defaultSize(width: 1100, height: 700)
.windowStyle(.hiddenTitleBar)
.windowToolbarStyle(.unified)
Auxiliary windows (inspector, settings) use .windowResizability(.contentSize).
When body recomputes: Any @State, @Binding, @Observable property change triggers body recomputation for views that read that property. SwiftUI uses structural identity (position in the view tree) to determine which views to update.
@State persistence: @State survives body recomputation but is destroyed when the view's structural identity changes (e.g., moved to a different branch of an if/else).
// BAD — identity changes on toggle, destroying @State in ChildView
if showAlternate {
ChildView() // This is a NEW ChildView each toggle
} else {
ChildView() // Different structural identity
}
// GOOD — stable identity
ChildView()
.opacity(showAlternate ? 0.5 : 1.0)
EquatableView for expensive bodies:
struct ExpensiveView: View, Equatable {
let data: LargeDataSet
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.data.id == rhs.data.id // Only recompute body if ID changes
}
var body: some View { /* expensive layout */ }
}
@Observable classes do NOT have the retain cycle risks of ObservableObject + @Published closures. SwiftUI observation tracking is automatic and non-retaining.
When to use weak:
selfNotificationCenter observersWhen weak is NOT needed:
@Observable ViewModels referenced by @State in Views (SwiftUI manages lifecycle)Task { } closures in @Observable classes (tasks are structured)// NotificationCenter — still needs weak
NotificationCenter.default.addObserver(
forName: .NSManagedObjectContextObjectsDidChange,
object: context, queue: .main
) { [weak self] _ in
Task { @MainActor in self?.refresh() }
}
testing
XCTest patterns for macOS Swift apps. Unit tests, async tests, Core Data tests, mock patterns, and assertion reference. Use when writing or reviewing tests.
tools
How to transition workflow state between review stages. Rules for setting review_stage and review_result fields on Stories and Epics.
documentation
Comment structure and rules for task workflow updates. Use when adding any comment to a task during implementation, review, or fix cycles.
testing
Validate task/story/epic/bug/techdebt metadata against schema v2.0. Run after TaskCreate or TaskUpdate to verify compliance. Returns pass/fail with actionable details.