diff --git a/README.md b/README.md index 063ca7dd..1ffa4aec 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ For manual install instructions see the README in the `rules/` folder. /plugin list everything-claude-code@everything-claude-code ``` -✨ **That's it!** You now have access to 13 agents, 44 skills, and 32 commands. +✨ **That's it!** You now have access to 13 agents, 48 skills, and 32 commands. --- @@ -252,6 +252,10 @@ everything-claude-code/ | |-- swift-actor-persistence/ # Thread-safe Swift data persistence with actors (NEW) | |-- swift-protocol-di-testing/ # Protocol-based DI for testable Swift code (NEW) | |-- search-first/ # Research-before-coding workflow (NEW) +| |-- skill-stocktake/ # Audit skills and commands for quality (NEW) +| |-- liquid-glass-design/ # iOS 26 Liquid Glass design system (NEW) +| |-- foundation-models-on-device/ # Apple on-device LLM with FoundationModels (NEW) +| |-- swift-concurrency-6-2/ # Swift 6.2 Approachable Concurrency (NEW) | |-- commands/ # Slash commands for quick execution | |-- tdd.md # /tdd - Test-driven development @@ -811,7 +815,7 @@ The configuration is automatically detected from `.opencode/opencode.json`. |---------|-------------|----------|--------| | Agents | ✅ 13 agents | ✅ 12 agents | **Claude Code leads** | | Commands | ✅ 32 commands | ✅ 24 commands | **Claude Code leads** | -| Skills | ✅ 44 skills | ✅ 16 skills | **Claude Code leads** | +| Skills | ✅ 48 skills | ✅ 16 skills | **Claude Code leads** | | Hooks | ✅ 3 phases | ✅ 20+ events | **OpenCode has more!** | | Rules | ✅ 8 rules | ✅ 8 rules | **Full parity** | | MCP Servers | ✅ Full | ✅ Full | **Full parity** | diff --git a/skills/foundation-models-on-device/SKILL.md b/skills/foundation-models-on-device/SKILL.md new file mode 100644 index 00000000..2304ca0e --- /dev/null +++ b/skills/foundation-models-on-device/SKILL.md @@ -0,0 +1,243 @@ +--- +name: foundation-models-on-device +description: Apple FoundationModels framework for on-device LLM — text generation, guided generation with @Generable, tool calling, and snapshot streaming in iOS 26+. +--- + +# FoundationModels: On-Device LLM (iOS 26) + +Patterns for integrating Apple's on-device language model into apps using the FoundationModels framework. Covers text generation, structured output with `@Generable`, custom tool calling, and snapshot streaming — all running on-device for privacy and offline support. + +## When to Activate + +- Building AI-powered features using Apple Intelligence on-device +- Generating or summarizing text without cloud dependency +- Extracting structured data from natural language input +- Implementing custom tool calling for domain-specific AI actions +- Streaming structured responses for real-time UI updates +- Need privacy-preserving AI (no data leaves the device) + +## Core Pattern — Availability Check + +Always check model availability before creating a session: + +```swift +struct GenerativeView: View { + private var model = SystemLanguageModel.default + + var body: some View { + switch model.availability { + case .available: + ContentView() + case .unavailable(.deviceNotEligible): + Text("Device not eligible for Apple Intelligence") + case .unavailable(.appleIntelligenceNotEnabled): + Text("Please enable Apple Intelligence in Settings") + case .unavailable(.modelNotReady): + Text("Model is downloading or not ready") + case .unavailable(let other): + Text("Model unavailable: \(other)") + } + } +} +``` + +## Core Pattern — Basic Session + +```swift +// Single-turn: create a new session each time +let session = LanguageModelSession() +let response = try await session.respond(to: "What's a good month to visit Paris?") +print(response.content) + +// Multi-turn: reuse session for conversation context +let session = LanguageModelSession(instructions: """ + You are a cooking assistant. + Provide recipe suggestions based on ingredients. + Keep suggestions brief and practical. + """) + +let first = try await session.respond(to: "I have chicken and rice") +let followUp = try await session.respond(to: "What about a vegetarian option?") +``` + +Key points for instructions: +- Define the model's role ("You are a mentor") +- Specify what to do ("Help extract calendar events") +- Set style preferences ("Respond as briefly as possible") +- Add safety measures ("Respond with 'I can't help with that' for dangerous requests") + +## Core Pattern — Guided Generation with @Generable + +Generate structured Swift types instead of raw strings: + +### 1. Define a Generable Type + +```swift +@Generable(description: "Basic profile information about a cat") +struct CatProfile { + var name: String + + @Guide(description: "The age of the cat", .range(0...20)) + var age: Int + + @Guide(description: "A one sentence profile about the cat's personality") + var profile: String +} +``` + +### 2. Request Structured Output + +```swift +let response = try await session.respond( + to: "Generate a cute rescue cat", + generating: CatProfile.self +) + +// Access structured fields directly +print("Name: \(response.content.name)") +print("Age: \(response.content.age)") +print("Profile: \(response.content.profile)") +``` + +### Supported @Guide Constraints + +- `.range(0...20)` — numeric range +- `.count(3)` — array element count +- `description:` — semantic guidance for generation + +## Core Pattern — Tool Calling + +Let the model invoke custom code for domain-specific tasks: + +### 1. Define a Tool + +```swift +struct RecipeSearchTool: Tool { + let name = "recipe_search" + let description = "Search for recipes matching a given term and return a list of results." + + @Generable + struct Arguments { + var searchTerm: String + var numberOfResults: Int + } + + func call(arguments: Arguments) async throws -> ToolOutput { + let recipes = await searchRecipes( + term: arguments.searchTerm, + limit: arguments.numberOfResults + ) + return .string(recipes.map { "- \($0.name): \($0.description)" }.joined(separator: "\n")) + } +} +``` + +### 2. Create Session with Tools + +```swift +let session = LanguageModelSession(tools: [RecipeSearchTool()]) +let response = try await session.respond(to: "Find me some pasta recipes") +``` + +### 3. Handle Tool Errors + +```swift +do { + let answer = try await session.respond(to: "Find a recipe for tomato soup.") +} catch let error as LanguageModelSession.ToolCallError { + print(error.tool.name) + if case .databaseIsEmpty = error.underlyingError as? RecipeSearchToolError { + // Handle specific tool error + } +} +``` + +## Core Pattern — Snapshot Streaming + +Stream structured responses for real-time UI with `PartiallyGenerated` types: + +```swift +@Generable +struct TripIdeas { + @Guide(description: "Ideas for upcoming trips") + var ideas: [String] +} + +let stream = session.streamResponse( + to: "What are some exciting trip ideas?", + generating: TripIdeas.self +) + +for try await partial in stream { + // partial: TripIdeas.PartiallyGenerated (all properties Optional) + print(partial) +} +``` + +### SwiftUI Integration + +```swift +@State private var partialResult: TripIdeas.PartiallyGenerated? +@State private var errorMessage: String? + +var body: some View { + List { + ForEach(partialResult?.ideas ?? [], id: \.self) { idea in + Text(idea) + } + } + .overlay { + if let errorMessage { Text(errorMessage).foregroundStyle(.red) } + } + .task { + do { + let stream = session.streamResponse(to: prompt, generating: TripIdeas.self) + for try await partial in stream { + partialResult = partial + } + } catch { + errorMessage = error.localizedDescription + } + } +} +``` + +## Key Design Decisions + +| Decision | Rationale | +|----------|-----------| +| On-device execution | Privacy — no data leaves the device; works offline | +| 4,096 token limit | On-device model constraint; chunk large data across sessions | +| Snapshot streaming (not deltas) | Structured output friendly; each snapshot is a complete partial state | +| `@Generable` macro | Compile-time safety for structured generation; auto-generates `PartiallyGenerated` type | +| Single request per session | `isResponding` prevents concurrent requests; create multiple sessions if needed | +| `response.content` (not `.output`) | Correct API — always access results via `.content` property | + +## Best Practices + +- **Always check `model.availability`** before creating a session — handle all unavailability cases +- **Use `instructions`** to guide model behavior — they take priority over prompts +- **Check `isResponding`** before sending a new request — sessions handle one request at a time +- **Access `response.content`** for results — not `.output` +- **Break large inputs into chunks** — 4,096 token limit applies to instructions + prompt + output combined +- **Use `@Generable`** for structured output — stronger guarantees than parsing raw strings +- **Use `GenerationOptions(temperature:)`** to tune creativity (higher = more creative) +- **Monitor with Instruments** — use Xcode Instruments to profile request performance + +## Anti-Patterns to Avoid + +- Creating sessions without checking `model.availability` first +- Sending inputs exceeding the 4,096 token context window +- Attempting concurrent requests on a single session +- Using `.output` instead of `.content` to access response data +- Parsing raw string responses when `@Generable` structured output would work +- Building complex multi-step logic in a single prompt — break into multiple focused prompts +- Assuming the model is always available — device eligibility and settings vary + +## When to Use + +- On-device text generation for privacy-sensitive apps +- Structured data extraction from user input (forms, natural language commands) +- AI-assisted features that must work offline +- Streaming UI that progressively shows generated content +- Domain-specific AI actions via tool calling (search, compute, lookup) diff --git a/skills/liquid-glass-design/SKILL.md b/skills/liquid-glass-design/SKILL.md new file mode 100644 index 00000000..60551c2a --- /dev/null +++ b/skills/liquid-glass-design/SKILL.md @@ -0,0 +1,279 @@ +--- +name: liquid-glass-design +description: iOS 26 Liquid Glass design system — dynamic glass material with blur, reflection, and interactive morphing for SwiftUI, UIKit, and WidgetKit. +--- + +# Liquid Glass Design System (iOS 26) + +Patterns for implementing Apple's Liquid Glass — a dynamic material that blurs content behind it, reflects color and light from surrounding content, and reacts to touch and pointer interactions. Covers SwiftUI, UIKit, and WidgetKit integration. + +## When to Activate + +- Building or updating apps for iOS 26+ with the new design language +- Implementing glass-style buttons, cards, toolbars, or containers +- Creating morphing transitions between glass elements +- Applying Liquid Glass effects to widgets +- Migrating existing blur/material effects to the new Liquid Glass API + +## Core Pattern — SwiftUI + +### Basic Glass Effect + +The simplest way to add Liquid Glass to any view: + +```swift +Text("Hello, World!") + .font(.title) + .padding() + .glassEffect() // Default: regular variant, capsule shape +``` + +### Customizing Shape and Tint + +```swift +Text("Hello, World!") + .font(.title) + .padding() + .glassEffect(.regular.tint(.orange).interactive(), in: .rect(cornerRadius: 16.0)) +``` + +Key customization options: +- `.regular` — standard glass effect +- `.tint(Color)` — add color tint for prominence +- `.interactive()` — react to touch and pointer interactions +- Shape: `.capsule` (default), `.rect(cornerRadius:)`, `.circle` + +### Glass Button Styles + +```swift +Button("Click Me") { /* action */ } + .buttonStyle(.glass) + +Button("Important") { /* action */ } + .buttonStyle(.glassProminent) +``` + +### GlassEffectContainer for Multiple Elements + +Always wrap multiple glass views in a container for performance and morphing: + +```swift +GlassEffectContainer(spacing: 40.0) { + HStack(spacing: 40.0) { + Image(systemName: "scribble.variable") + .frame(width: 80.0, height: 80.0) + .font(.system(size: 36)) + .glassEffect() + + Image(systemName: "eraser.fill") + .frame(width: 80.0, height: 80.0) + .font(.system(size: 36)) + .glassEffect() + } +} +``` + +The `spacing` parameter controls merge distance — closer elements blend their glass shapes together. + +### Uniting Glass Effects + +Combine multiple views into a single glass shape with `glassEffectUnion`: + +```swift +@Namespace private var namespace + +GlassEffectContainer(spacing: 20.0) { + HStack(spacing: 20.0) { + ForEach(symbolSet.indices, id: \.self) { item in + Image(systemName: symbolSet[item]) + .frame(width: 80.0, height: 80.0) + .glassEffect() + .glassEffectUnion(id: item < 2 ? "group1" : "group2", namespace: namespace) + } + } +} +``` + +### Morphing Transitions + +Create smooth morphing when glass elements appear/disappear: + +```swift +@State private var isExpanded = false +@Namespace private var namespace + +GlassEffectContainer(spacing: 40.0) { + HStack(spacing: 40.0) { + Image(systemName: "scribble.variable") + .frame(width: 80.0, height: 80.0) + .glassEffect() + .glassEffectID("pencil", in: namespace) + + if isExpanded { + Image(systemName: "eraser.fill") + .frame(width: 80.0, height: 80.0) + .glassEffect() + .glassEffectID("eraser", in: namespace) + } + } +} + +Button("Toggle") { + withAnimation { isExpanded.toggle() } +} +.buttonStyle(.glass) +``` + +### Extending Horizontal Scrolling Under Sidebar + +To allow horizontal scroll content to extend under a sidebar or inspector, ensure the `ScrollView` content reaches the leading/trailing edges of the container. The system automatically handles the under-sidebar scrolling behavior when the layout extends to the edges — no additional modifier is needed. + +## Core Pattern — UIKit + +### Basic UIGlassEffect + +```swift +let glassEffect = UIGlassEffect() +glassEffect.tintColor = UIColor.systemBlue.withAlphaComponent(0.3) +glassEffect.isInteractive = true + +let visualEffectView = UIVisualEffectView(effect: glassEffect) +visualEffectView.translatesAutoresizingMaskIntoConstraints = false +visualEffectView.layer.cornerRadius = 20 +visualEffectView.clipsToBounds = true + +view.addSubview(visualEffectView) +NSLayoutConstraint.activate([ + visualEffectView.centerXAnchor.constraint(equalTo: view.centerXAnchor), + visualEffectView.centerYAnchor.constraint(equalTo: view.centerYAnchor), + visualEffectView.widthAnchor.constraint(equalToConstant: 200), + visualEffectView.heightAnchor.constraint(equalToConstant: 120) +]) + +// Add content to contentView +let label = UILabel() +label.text = "Liquid Glass" +label.translatesAutoresizingMaskIntoConstraints = false +visualEffectView.contentView.addSubview(label) +NSLayoutConstraint.activate([ + label.centerXAnchor.constraint(equalTo: visualEffectView.contentView.centerXAnchor), + label.centerYAnchor.constraint(equalTo: visualEffectView.contentView.centerYAnchor) +]) +``` + +### UIGlassContainerEffect for Multiple Elements + +```swift +let containerEffect = UIGlassContainerEffect() +containerEffect.spacing = 40.0 + +let containerView = UIVisualEffectView(effect: containerEffect) + +let firstGlass = UIVisualEffectView(effect: UIGlassEffect()) +let secondGlass = UIVisualEffectView(effect: UIGlassEffect()) + +containerView.contentView.addSubview(firstGlass) +containerView.contentView.addSubview(secondGlass) +``` + +### Scroll Edge Effects + +```swift +scrollView.topEdgeEffect.style = .automatic +scrollView.bottomEdgeEffect.style = .hard +scrollView.leftEdgeEffect.isHidden = true +``` + +### Toolbar Glass Integration + +```swift +let favoriteButton = UIBarButtonItem(image: UIImage(systemName: "heart"), style: .plain, target: self, action: #selector(favoriteAction)) +favoriteButton.hidesSharedBackground = true // Opt out of shared glass background +``` + +## Core Pattern — WidgetKit + +### Rendering Mode Detection + +```swift +struct MyWidgetView: View { + @Environment(\.widgetRenderingMode) var renderingMode + + var body: some View { + if renderingMode == .accented { + // Tinted mode: white-tinted, themed glass background + } else { + // Full color mode: standard appearance + } + } +} +``` + +### Accent Groups for Visual Hierarchy + +```swift +HStack { + VStack(alignment: .leading) { + Text("Title") + .widgetAccentable() // Accent group + Text("Subtitle") + // Primary group (default) + } + Image(systemName: "star.fill") + .widgetAccentable() // Accent group +} +``` + +### Image Rendering in Accented Mode + +```swift +Image("myImage") + .widgetAccentedRenderingMode(.monochrome) +``` + +### Container Background + +```swift +VStack { /* content */ } + .containerBackground(for: .widget) { + Color.blue.opacity(0.2) + } +``` + +## Key Design Decisions + +| Decision | Rationale | +|----------|-----------| +| GlassEffectContainer wrapping | Performance optimization, enables morphing between glass elements | +| `spacing` parameter | Controls merge distance — fine-tune how close elements must be to blend | +| `@Namespace` + `glassEffectID` | Enables smooth morphing transitions on view hierarchy changes | +| `interactive()` modifier | Explicit opt-in for touch/pointer reactions — not all glass should respond | +| UIGlassContainerEffect in UIKit | Same container pattern as SwiftUI for consistency | +| Accented rendering mode in widgets | System applies tinted glass when user selects tinted Home Screen | + +## Best Practices + +- **Always use GlassEffectContainer** when applying glass to multiple sibling views — it enables morphing and improves rendering performance +- **Apply `.glassEffect()` after** other appearance modifiers (frame, font, padding) +- **Use `.interactive()`** only on elements that respond to user interaction (buttons, toggleable items) +- **Choose spacing carefully** in containers to control when glass effects merge +- **Use `withAnimation`** when changing view hierarchies to enable smooth morphing transitions +- **Test across appearances** — light mode, dark mode, and accented/tinted modes +- **Ensure accessibility contrast** — text on glass must remain readable + +## Anti-Patterns to Avoid + +- Using multiple standalone `.glassEffect()` views without a GlassEffectContainer +- Nesting too many glass effects — degrades performance and visual clarity +- Applying glass to every view — reserve for interactive elements, toolbars, and cards +- Forgetting `clipsToBounds = true` in UIKit when using corner radii +- Ignoring accented rendering mode in widgets — breaks tinted Home Screen appearance +- Using opaque backgrounds behind glass — defeats the translucency effect + +## When to Use + +- Navigation bars, toolbars, and tab bars with the new iOS 26 design +- Floating action buttons and card-style containers +- Interactive controls that need visual depth and touch feedback +- Widgets that should integrate with the system's Liquid Glass appearance +- Morphing transitions between related UI states diff --git a/skills/swift-concurrency-6-2/SKILL.md b/skills/swift-concurrency-6-2/SKILL.md new file mode 100644 index 00000000..d9864cc4 --- /dev/null +++ b/skills/swift-concurrency-6-2/SKILL.md @@ -0,0 +1,216 @@ +--- +name: swift-concurrency-6-2 +description: Swift 6.2 Approachable Concurrency — single-threaded by default, @concurrent for explicit background offloading, isolated conformances for main actor types. +--- + +# Swift 6.2 Approachable Concurrency + +Patterns for adopting Swift 6.2's concurrency model where code runs single-threaded by default and concurrency is introduced explicitly. Eliminates common data-race errors without sacrificing performance. + +## When to Activate + +- Migrating Swift 5.x or 6.0/6.1 projects to Swift 6.2 +- Resolving data-race safety compiler errors +- Designing MainActor-based app architecture +- Offloading CPU-intensive work to background threads +- Implementing protocol conformances on MainActor-isolated types +- Enabling Approachable Concurrency build settings in Xcode 26 + +## Core Problem: Implicit Background Offloading + +In Swift 6.1 and earlier, async functions could be implicitly offloaded to background threads, causing data-race errors even in seemingly safe code: + +```swift +// Swift 6.1: ERROR +@MainActor +final class StickerModel { + let photoProcessor = PhotoProcessor() + + func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? { + guard let data = try await item.loadTransferable(type: Data.self) else { return nil } + + // Error: Sending 'self.photoProcessor' risks causing data races + return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier) + } +} +``` + +Swift 6.2 fixes this: async functions stay on the calling actor by default. + +```swift +// Swift 6.2: OK — async stays on MainActor, no data race +@MainActor +final class StickerModel { + let photoProcessor = PhotoProcessor() + + func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? { + guard let data = try await item.loadTransferable(type: Data.self) else { return nil } + return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier) + } +} +``` + +## Core Pattern — Isolated Conformances + +MainActor types can now conform to non-isolated protocols safely: + +```swift +protocol Exportable { + func export() +} + +// Swift 6.1: ERROR — crosses into main actor-isolated code +// Swift 6.2: OK with isolated conformance +extension StickerModel: @MainActor Exportable { + func export() { + photoProcessor.exportAsPNG() + } +} +``` + +The compiler ensures the conformance is only used on the main actor: + +```swift +// OK — ImageExporter is also @MainActor +@MainActor +struct ImageExporter { + var items: [any Exportable] + + mutating func add(_ item: StickerModel) { + items.append(item) // Safe: same actor isolation + } +} + +// ERROR — nonisolated context can't use MainActor conformance +nonisolated struct ImageExporter { + var items: [any Exportable] + + mutating func add(_ item: StickerModel) { + items.append(item) // Error: Main actor-isolated conformance cannot be used here + } +} +``` + +## Core Pattern — Global and Static Variables + +Protect global/static state with MainActor: + +```swift +// Swift 6.1: ERROR — non-Sendable type may have shared mutable state +final class StickerLibrary { + static let shared: StickerLibrary = .init() // Error +} + +// Fix: Annotate with @MainActor +@MainActor +final class StickerLibrary { + static let shared: StickerLibrary = .init() // OK +} +``` + +### MainActor Default Inference Mode + +Swift 6.2 introduces a mode where MainActor is inferred by default — no manual annotations needed: + +```swift +// With MainActor default inference enabled: +final class StickerLibrary { + static let shared: StickerLibrary = .init() // Implicitly @MainActor +} + +final class StickerModel { + let photoProcessor: PhotoProcessor + var selection: [PhotosPickerItem] // Implicitly @MainActor +} + +extension StickerModel: Exportable { // Implicitly @MainActor conformance + func export() { + photoProcessor.exportAsPNG() + } +} +``` + +This mode is opt-in and recommended for apps, scripts, and other executable targets. + +## Core Pattern — @concurrent for Background Work + +When you need actual parallelism, explicitly offload with `@concurrent`: + +> **Important:** This example requires Approachable Concurrency build settings — SE-0466 (MainActor default isolation) and SE-0461 (NonisolatedNonsendingByDefault). With these enabled, `extractSticker` stays on the caller's actor, making mutable state access safe. **Without these settings, this code has a data race** — the compiler will flag it. + +```swift +nonisolated final class PhotoProcessor { + private var cachedStickers: [String: Sticker] = [:] + + func extractSticker(data: Data, with id: String) async -> Sticker { + if let sticker = cachedStickers[id] { + return sticker + } + + let sticker = await Self.extractSubject(from: data) + cachedStickers[id] = sticker + return sticker + } + + // Offload expensive work to concurrent thread pool + @concurrent + static func extractSubject(from data: Data) async -> Sticker { /* ... */ } +} + +// Callers must await +let processor = PhotoProcessor() +processedPhotos[item.id] = await processor.extractSticker(data: data, with: item.id) +``` + +To use `@concurrent`: +1. Mark the containing type as `nonisolated` +2. Add `@concurrent` to the function +3. Add `async` if not already asynchronous +4. Add `await` at call sites + +## Key Design Decisions + +| Decision | Rationale | +|----------|-----------| +| Single-threaded by default | Most natural code is data-race free; concurrency is opt-in | +| Async stays on calling actor | Eliminates implicit offloading that caused data-race errors | +| Isolated conformances | MainActor types can conform to protocols without unsafe workarounds | +| `@concurrent` explicit opt-in | Background execution is a deliberate performance choice, not accidental | +| MainActor default inference | Reduces boilerplate `@MainActor` annotations for app targets | +| Opt-in adoption | Non-breaking migration path — enable features incrementally | + +## Migration Steps + +1. **Enable in Xcode**: Swift Compiler > Concurrency section in Build Settings +2. **Enable in SPM**: Use `SwiftSettings` API in package manifest +3. **Use migration tooling**: Automatic code changes via swift.org/migration +4. **Start with MainActor defaults**: Enable inference mode for app targets +5. **Add `@concurrent` where needed**: Profile first, then offload hot paths +6. **Test thoroughly**: Data-race issues become compile-time errors + +## Best Practices + +- **Start on MainActor** — write single-threaded code first, optimize later +- **Use `@concurrent` only for CPU-intensive work** — image processing, compression, complex computation +- **Enable MainActor inference mode** for app targets that are mostly single-threaded +- **Profile before offloading** — use Instruments to find actual bottlenecks +- **Protect globals with MainActor** — global/static mutable state needs actor isolation +- **Use isolated conformances** instead of `nonisolated` workarounds or `@Sendable` wrappers +- **Migrate incrementally** — enable features one at a time in build settings + +## Anti-Patterns to Avoid + +- Applying `@concurrent` to every async function (most don't need background execution) +- Using `nonisolated` to suppress compiler errors without understanding isolation +- Keeping legacy `DispatchQueue` patterns when actors provide the same safety +- Skipping `model.availability` checks in concurrency-related Foundation Models code +- Fighting the compiler — if it reports a data race, the code has a real concurrency issue +- Assuming all async code runs in the background (Swift 6.2 default: stays on calling actor) + +## When to Use + +- All new Swift 6.2+ projects (Approachable Concurrency is the recommended default) +- Migrating existing apps from Swift 5.x or 6.0/6.1 concurrency +- Resolving data-race safety compiler errors during Xcode 26 adoption +- Building MainActor-centric app architectures (most UI apps) +- Performance optimization — offloading specific heavy computations to background