git:20251203.46a896d to git:20260906.756f678

227 added, 137 removed. Audit A to A.

---
name: macos-programmer
description: macOS-specific development patterns, platform APIs, and decision frameworks. Use when developing Mac apps, macOS applications, Cocoa/AppKit code, or making SwiftUI vs AppKit decisions. Covers NSWindow management, NSDocument architecture, sandboxing, code signing, notarization, and macOS UI patterns. Applies to Mac Catalyst considerations.
---
# macOS Programming
<skill_scope skill="macos-programmer">
**Related skills:**
- `swift-programmer` — Swift language fundamentals and Swift 6 concurrency
- `software-engineer` — General software engineering principles and system architecture
- `test-driven-development` — Testing philosophy and practices
This skill covers macOS-specific development patterns, platform APIs, and decision frameworks. It applies when developing Mac apps, working with Cocoa/AppKit code, or making SwiftUI vs AppKit decisions.
</skill_scope>
## Core Philosophy
<core_philosophy>
- **Critical Reality (2024-2025):** SwiftUI for macOS is powerful but incomplete for production apps. Expert macOS developers must master BOTH SwiftUI and AppKit—SwiftUI for 70% of UI, AppKit for the 30% that makes professional apps professional. This hybrid approach is not a stopgap; it's the current production reality.
+ **Hybrid by default.** Production Mac apps commonly combine SwiftUI and AppKit: SwiftUI for most view content, AppKit where the platform's window, text, and event systems are exposed only there. Where the line falls varies by app; the case studies in `<swiftui_vs_appkit_decision>` show teams landing at different points, and each macOS release moves some formerly AppKit-only capability into SwiftUI (see `<recent_changes>`). Treat AppKit fallback as expected work rather than a failure of SwiftUI, and re-check the boundary when the deployment target moves.
**Platform Identity:** macOS is not iOS with a bigger screen. Multiple windows, menu bars, keyboard navigation, document-based architecture, and precise window management are first-class citizens. Respect macOS conventions; don't port iOS patterns blindly.
</core_philosophy>
## SwiftUI vs AppKit: The Critical Decision Framework
<swiftui_vs_appkit_decision>
**The Ground Truth from Production Apps:**
- SwiftUI maturity varies dramatically by platform. The Ghostty Terminal case study demonstrates this: had to completely rip out SwiftUI's app/window lifecycle (+802/-239 lines) just to implement non-native fullscreen.[^ghostty-devlog] Multi.app's approach: moved to SwiftUI but noted they were "approaching the cusp of dropping macOS 11 support entirely" due to SwiftUI bugs on older versions.[^multi-swiftui]
+ SwiftUI maturity differs between iOS and macOS. Two 2023 accounts from Mac apps in development show the shape of the gap. Ghostty (then in private beta) rewrote its SwiftUI app and window lifecycle management in AppKit (+802/-239 lines) when non-native fullscreen, which requires subclassing `NSWindow`, proved impossible in pure SwiftUI; its views stayed SwiftUI.[^ghostty-devlog] Multi.app moved to SwiftUI but still needed "some access to NSEvents, text input, and tweaking the first responder that just aren't possible with pure SwiftUI," and wrote that SwiftUI bugs on older macOS left them "approaching the cusp of dropping support entirely" for those versions.[^multi-swiftui] Both are dated; re-read the boundary against the current release before applying them.
**Use SwiftUI When:**
- New apps targeting macOS 14+, simple-to-medium complexity
- Standard UI elements suffice (lists, forms, navigation)
- Cross-platform iOS/macOS with acceptable compromises
- Rapid prototyping where bugs are acceptable
- - Team willing to use NSViewRepresentable for 20-30% of features
- - Can afford to drop users on macOS <14 (SwiftUI bugs improved significantly in Sonoma)
+ - Team willing to bridge to AppKit with `NSViewRepresentable` where a control or behavior isn't available in SwiftUI
+ - Can require a recent macOS; each release fixes SwiftUI-on-Mac issues, so older deployment targets carry more workarounds
**Use AppKit When:**
- Complex text editing (code editors, word processors, NSTextView-dependent workflows)
- - Large datasets (1000+ items—SwiftUI List has significant performance issues compared to NSTableView, especially during scrolling)
+ - Large datasets where profiling on a Release build shows SwiftUI `List` falling behind `NSTableView`; measure on the current OS, since a macOS 26 change to `NavigationLink` "improves performance of many `NavigationLink`s in lazy containers like `List`"[^macos26-notes]
- Custom window management (non-standard fullscreen, window subclassing, utility panels)
- - Performance-critical UI requiring 0% CPU baseline
- - Production apps where bugs equal revenue loss
- - Professional tools (IDEs, DAWs, design apps, terminals)
- - Rock-solid stability across OS versions
+ - UI that must idle at near-zero CPU (verify with Instruments rather than assuming either framework)
+ - Behavior that has been stable in AppKit across the OS versions you support, where the SwiftUI equivalent has changed release to release (check release notes for the specific control)
+ - Professional tools (IDEs, DAWs, design apps, terminals) whose text, window, or event needs exceed SwiftUI's surface
- **The Hybrid Reality (Recommended for Production):**
+ **The Hybrid Shape (Common in Production):**
- Multi.app's proven strategy: AppKit for window/app lifecycle, SwiftUI for views where appropriate, bridging via NSHostingController and NSViewRepresentable.
+ Ghostty's arrangement after its rewrite: AppKit owns the app and window lifecycle and SwiftUI supplies the views.[^ghostty-devlog] The bridging mechanisms are `NSHostingController` (SwiftUI inside AppKit) and `NSViewRepresentable` (AppKit inside SwiftUI).
<swiftui_limitations>
- **Key SwiftUI Limitations (as of macOS 15):**
- - TextField doesn't update bindings per-character (works on iOS)
- - List performance: seconds to render vs instant on iOS for identical code
- - Cannot fully customize menu bar like AppKit
- - Window subclassing impossible in pure SwiftUI
- - Limited responder chain access
- - Memory: SwiftUI on macOS can have memory issues (developers have reported significant memory growth compared to iOS for similar apps)
+ **SwiftUI Limitations on macOS (as of macOS 26; re-check each release):**
+ - Window subclassing is unavailable in pure SwiftUI; non-native fullscreen and similar behaviors need AppKit[^ghostty-devlog]
+ - Direct access to `NSEvent`, first-responder manipulation, and some text-input behavior is AppKit-only[^multi-swiftui]
+ - The menu bar cannot be customized to the degree AppKit allows
+ - `List` performance with large data sets can lag `NSTableView`; the gap is workload-dependent and narrowed in macOS 26, so measure rather than assume
+ - Memory growth relative to iOS for similar apps has been reported by developers; treat it as something to profile, not a settled property
+
+ Capabilities that have moved into SwiftUI recently, such as styled text editing with `AttributedString` and Find Bar control in `TextEditor` on macOS 26, are listed in `<recent_changes>`.
</swiftui_limitations>
**Decision Pattern:**
```
Production Mac App
├── AppKit: NSApplication, NSWindow, NSWindowController, NSDocument
├── SwiftUI: View content where appropriate
└── Bridge: NSHostingController, NSViewRepresentable
```
</swiftui_vs_appkit_decision>
## Platform Differences from iOS (Critical for iOS Developers)
<platform_differences>
**Coordinate Systems:**
- iOS origin: top-left, Y increases downward
- - macOS origin: bottom-left, Y increases upward (mathematical convention)
+ - macOS (unflipped `NSView`) origin: bottom-left, Y increases upward
- Override `isFlipped` to return `true` for iOS-style coordinates
- - Drawing images without compensating results in upside-down images
+ - Drawing into a flipped context without compensating can draw images upside down; check the drawing API's handling of flipped contexts
**Layer Backing:**
- iOS views are layer-backed by default
- - macOS requires explicit `wantsLayer = true`
- - Layer-backed views enable GPU compositing but cost memory
- - Use for animations and effects, not for static content
+ - macOS views are not layer-backed unless the view or an ancestor sets `wantsLayer = true`
+ - Layer-backed views enable GPU compositing but cost memory; a layer-backed view's subviews become layer-backed too
+ - Enable it for animation and compositing effects; leave static content unbacked unless profiling shows a benefit
**Windows vs Views:**
- macOS users expect multiple windows, resizing, minimize/maximize
- NSWindow is critical—not a passive container like UIWindow
- Window management patterns (tabs, fullscreen, spaces) are first-class
- Custom window behaviors require AppKit (SwiftUI limitations)
**Text System:**
- NSTextView/TextKit vastly more powerful than UITextView
- Rulers, find/replace, grammar checking built-in
- - TextKit 2 (macOS 13+) has production stability issues as of 2024
- - Force TextKit 1 with `let _ = textView.layoutManager`
+ - TextKit 2 shipped in macOS 12 and became the default for all text controls, `NSTextView` included, in macOS 13;[^wwdc22-10090] touching `textView.layoutManager` switches that view to TextKit 1 compatibility mode ("if you explicitly call the `layoutManager` property on a text view or text container, the framework reverts to a compatibility mode")[^textkit-compat]
+ - When a view needs TextKit 1 (a reproduced TextKit 2 regression, or code that depends on `NSLayoutManager` during a migration), select it when creating the view rather than by triggering the fallback later, which discards the TextKit 2 layout; macOS 26 continues to extend TextKit 2 (e.g., `includesTextListMarkers`)[^macos26-notes]
**Background Colors:**
- Many NSView subclasses use `drawsBackground` property
- Not universal `backgroundColor` like iOS
- Check class documentation for correct property
**Mouse vs Touch:**
- - Must implement `updateTrackingAreas()` for hover effects
+ - AppKit hover effects need tracking areas (`updateTrackingAreas()`); SwiftUI views use `.onHover`
- Right-click context menus are standard expectation
- Mouse tracking differs from touch gesture handling
- `NSEvent` provides precise cursor position and modifier keys
</platform_differences>
## Window Management Mastery
<window_management>
- **NSWindow Lifecycle (macOS 10.13+ Change):**
+ **NSWindow Lifecycle (10.13 SDK Change):**
- Pre-10.13 behavior required `releasedWhenClosed = NO` to prevent over-release crashes.
+ `isReleasedWhenClosed` defaults to `true` for `NSWindow` (`false` for `NSPanel`) and "is ignored for windows owned by window controllers";[^released-when-closed] for a window your own code owns and references, set it to `false`, or closing the window over-releases it under ARC.
- macOS 10.13+ SDK: NSWindows that are ordered-in are strongly referenced by AppKit until explicitly ordered-out or closed. Uses `CFExecutableLinkedOnOrAfter` check—deployment target 10.12 or lower cannot rely on new behavior even on 10.13+.
+ AppKit's release notes state the current rule: "If your application is linked on macOS 10.13 SDK or later, NSWindows that are ordered-in will be strongly referenced by AppKit, until they are explicitly ordered-out or closed."[^appkit-rn-window] The condition is the SDK the app is linked against, not its deployment target; an app built against an older SDK keeps the old behavior even when running on newer macOS.
**Window Style and Collection Behavior:**
Style masks combine but have limitations:
- - Borderless windows can't become key by default (workaround: subclass, override `canBecomeKey` → true)
- - Style changes during fullscreen are unreliable
- - Full-size content view opts into layer-backing automatically
+ - A borderless window "can't become key or main, unless the value of `canBecomeKey` or `canBecomeMain` is `true`" (subclass and override)[^stylemask-borderless]
+ - "Changing the style mask may cause the view hierarchy to be rebuilt,"[^stylemask] so avoid changing it mid-animation or mid-fullscreen-transition
+ - `fullSizeContentView` "opts in to layer-backing"[^stylemask-fullsize]
Collection behavior controls Spaces/Exposé/fullscreen:
- `.canJoinAllSpaces`: Visible on all spaces (like menu bar)
- - `.moveToActiveSpace`: Follows when space changes
+ - `.moveToActiveSpace`: "When the window becomes active, move it to the active space instead of switching spaces"[^collection-behavior]
- `.fullScreenPrimary`: Can be fullscreen window
- `.fullScreenAuxiliary`: Shown with fullscreen window
- `.stationary`: Unaffected by Exposé, visible on desktop
- **Pattern for always-visible overlay:**
+ **Pattern for an overlay that appears on every space:**
```swift
window.collectionBehavior = [.canJoinAllSpaces, .stationary]
```
+ Collection behavior governs Spaces and Exposé membership, not stacking order or fullscreen coexistence; set the window level separately and test with a fullscreen app and Stage Manager before relying on it.
**Multi-Window Document Architecture:**
```
NSDocumentController (singleton)
↓ manages
NSDocument instances (one per document)
↓ manages
NSWindowController instances (one per window)
```
**Modern Document Best Practice:**
```swift
override class var autosavesInPlace: Bool { true }
```
- Enables automatic version browsing, asynchronous saving, system-managed version storage.
+ Enables autosave in place and the system's version browsing and storage. Asynchronous saving is a separate opt-in (`canAsynchronouslyWrite(to:ofType:for:)`), and the document must still unblock user interaction itself.
</window_management>
## Responder Chain and Menu Validation
<responder_chain>
**The Complete Action Message Responder Chain:**
- 1. Start with firstResponder in key window
- 2. Try every nextResponder in chain
- 3. Try key window's delegate
- 4. Try same for main window (if different from key)
- 5. NSApplication tries to respond
- 6. NSApplication.delegate is tried last
+ 1. Start with the first responder in the key window
+ 2. Try every `nextResponder` in that chain, then the key window itself
+ 3. Try the key window's delegate, then its `NSDocument` (if different from the delegate)
+ 4. Repeat for the main window, if it is a different window
+ 5. `NSApplication` tries to respond
+ 6. `NSApplication.delegate`
+ 7. In a document-based app, the `NSDocumentController` (which does not inherit from `NSResponder`)[^event-architecture]
**Critical Insight:** App delegate is NOT part of nextResponder chain—you can never reach it through iteration. It's used as a fallback when current key window's responder chain returns nil.
- **NSViewController Integration (Yosemite 10.10+):**
-
- Pre-10.10: NSViewControllers were NOT in responder chain by default—had to manually patch nextResponder.
+ **NSViewController Integration (macOS 10.10+):**
- Yosemite+: Automatically wires view controllers right after their view, but ONLY if the view controller is added as a child view controller to a parent controller.
+ Before 10.10, an `NSViewController` was not in the responder chain by default; code patched `nextResponder` by hand. From 10.10, AppKit inserts the view controller into the chain immediately after its view: "The view's nextResponder is then set to be the viewController, and viewController's nextResponder is set to be the previously saved nextResponder."[^appkit-rn-1010]
**Menu Validation Performance:**
NSMenu updates EVERY menu item on EVERY user event (mouse move, keypress). This is a performance killer for large menus.
How it works:
1. Determine item's target (explicit or via responder chain)
2. Check if target implements action method (if not, disable)
3. If target implements `validateMenuItem:` or `validateUserInterfaceItem:`, call it and use return value
**Optimization:**
- Disable auto-validation for static menus: `menu.autoenablesItems = false`
- Manually control `menuItem.isEnabled`
- - Target must be set for validation to be called
+ - A `nil` target routes the action and the validation query through the responder chain; set an explicit target only when you want to bypass that lookup
</responder_chain>
## SwiftUI Integration with AppKit
<swiftui_appkit_integration>
**NSHostingController (Essential Bridge Pattern):**
```swift
// Embedding SwiftUI in AppKit
let swiftUIView = MySwiftUIView()
let hostingController = NSHostingController(rootView: swiftUIView)
// macOS 13+ sizing control
hostingController.sizingOptions = [.intrinsicContentSize]
```
- **NSViewRepresentable (Critical Pattern):**
+ **NSViewRepresentable:**
- The golden rule: ONLY update NSView properties when they've changed. Otherwise you create infinite loops.
+ `updateNSView` runs whenever SwiftUI updates this represented view, so guard assignments whose setter has side effects: assigning `NSTextView.string` resets the selection (observed on macOS 26; it does not post `textDidChange`). Propagate edits back to the binding through a `Coordinator`, or the bridge is one-way.
```swift
struct TextViewRepresentable: NSViewRepresentable {
@Binding var text: String
+ func makeCoordinator() -> Coordinator { Coordinator(text: $text) }
+
+ func makeNSView(context: Context) -> NSTextView {
+ let view = NSTextView()
+ view.delegate = context.coordinator
+ return view
+ }
+
func updateNSView(_ nsView: NSTextView, context: Context) {
- // CRITICAL: Check before setting
- if nsView.string != text {
+ context.coordinator.text = $text // keep the coordinator on the current binding
+ if nsView.string != text { // guard: assigning resets the selection
nsView.string = text
}
}
+
+ final class Coordinator: NSObject, NSTextViewDelegate {
+ var text: Binding<String>
+ init(text: Binding<String>) { self.text = text }
+ func textDidChange(_ notification: Notification) {
+ guard let view = notification.object as? NSTextView else { return }
+ text.wrappedValue = view.string // edits flow back to SwiftUI
+ }
+ }
}
```
- **State Management with @Observable (macOS 14+):**
+ **State Management with `@MainActor @Observable` (macOS 14+):**
- Critical gotcha: @State with @Observable calls init() on EVERY view rebuild.
+ Gotcha, toolchain-dependent: built with Xcode 26 or earlier, "A `State` property always instantiates its default value when SwiftUI instantiates the view," so Apple's guidance is to "avoid side effects and performance-intensive work when initializing the default value";[^swiftui-state] an `@Observable` model declared as `@State` in a frequently re-instantiated view is allocated on each instantiation. Built with Xcode 27 (beta 6 as of September 2026), `@State` is a macro and "objects held in state are only ever initialized one time, when the view is first created," which removes the cost but also rejects some initializer patterns that used to compile; read TN3211 before migrating.[^tn3211] For SwiftUI view state, keep the observable type on the main actor; see `swift-programmer` for the general `@MainActor @Observable` rule.
- Solution: Declare in App struct:
+ Solution for app-wide state: declare the main-actor observable model in the `App` struct, which SwiftUI instantiates once. Apple's alternative for a view-local model is to create it in a `.task` modifier, "which is called only once when the view first appears";[^swiftui-state] that is once per appearance of a given identity: it runs again if the view disappears and reappears, or if its identity changes, so guard the creation if it must happen once per state lifetime.
```swift
+ @MainActor
+ @Observable
+ class AppModel {
+ // App state and actions.
+ }
+
@main
struct MyApp: App {
@State private var appModel = AppModel() // Declare here
var body: some Scene {
WindowGroup {
ContentView().environment(appModel)
}
}
}
```
**Multi-Window Management:**
```swift
// WindowGroup - Multiple instances
WindowGroup { ContentView() }
// Window - Single unique instance
Window("Stats", id: "stats") { StatsView() }
- // UtilityWindow (macOS 14+) - Floating palette
+ // UtilityWindow (macOS 15+) - Floating palette
UtilityWindow("Palette", id: "palette") { PaletteView() }
.keyboardShortcut("u")
```
**Menu Bar & Commands:**
```swift
.commands {
CommandMenu("Custom") {
Button("Action") {}
.keyboardShortcut("x", modifiers: [.command, .shift])
}
}
// Focus values for multi-window menus
@FocusedValue(\.messageState) var messageState
```
</swiftui_appkit_integration>
## Sandboxing and File System Access
<sandboxing>
**Sandboxing Strategy:**
- Mac App Store: REQUIRED
- Direct Distribution: OPTIONAL but strongly recommended
**Access Methods:**
1. **User Selection** (NSOpenPanel/NSSavePanel): Immediate access
2. **Security-Scoped Bookmarks:** Persistent access across launches
- 3. **Container Access:** Automatic for `~/Library/Containers/<bundle-id>`
+ 3. **Container Access:** Automatic for `~/Library/Containers/{bundle-id}`
**Security-Scoped Bookmarks (Critical Pattern):**
```swift
// Save bookmark
let bookmarkData = try url.bookmarkData(options: .withSecurityScope)
// Restore and use
- let url = try URL(resolvingBookmarkData: bookmarkData, options: .withSecurityScope)
- guard url.startAccessingSecurityScopedResource() else { throw error }
+ var isStale = false
+ let url = try URL(resolvingBookmarkData: bookmarkData,
+ options: .withSecurityScope,
+ bookmarkDataIsStale: &isStale)
+ guard url.startAccessingSecurityScopedResource() else {
+ throw CocoaError(.fileReadNoPermission)
+ }
defer { url.stopAccessingSecurityScopedResource() }
+ if isStale {
+ // Re-create and re-save the bookmark while access is active; creating one needs access to the file.
+ let fresh = try url.bookmarkData(options: .withSecurityScope)
+ save(fresh)
+ }
// Access file
```
- **Critical Rules:**
- - Call `startAccessingSecurityScopedResource()` on resolved URL, not original
- - Don't call for NSOpenPanel URLs (temporary access granted)
- - Always pair start/stop calls—NOT nested
- - Leaking access consumes kernel resources, requiring app relaunch
+ **Rules:**
+ - Call `startAccessingSecurityScopedResource()` on the resolved URL, not the original
+ - Don't call it for `NSOpenPanel`/`NSSavePanel` URLs; the system starts access on those for you
+ - Balance every successful start with a stop; calls may nest, and access ends at the last balanced stop[^security-scoped]
+ - Leaking access consumes kernel resources until the app relaunches
**Entitlements to Know:**
- `com.apple.security.app-sandbox`: Enable App Sandbox
- `com.apple.security.files.user-selected.read-write`: User-selected files
- `com.apple.security.files.bookmarks.app-scope`: App-scoped bookmarks
- `com.apple.security.network.client`: Outgoing network connections
- `com.apple.security.network.server`: Incoming network connections
</sandboxing>
## Code Signing and Notarization
<code_signing>
- **Current Process (2024-2025):**
+ **Process (checked against Apple's notarization documentation, September 2026):**
- 1. **Code Sign:**
+ 1. **Code Sign** each nested component first (frameworks, helpers, plug-ins), then the app, without `--deep`:
```bash
- codesign --deep --force --options runtime \
+ codesign --force --options runtime --timestamp \
--entitlements App.entitlements \
--sign "Developer ID Application: Your Name (TEAMID)" \
App.app
- codesign --verify --verbose App.app
+ codesign --verify --deep --strict --verbose=2 App.app # --deep is for verification
```
2. **Create Archive:**
```bash
ditto -c -k --keepParent App.app App.zip
```
- 3. **Submit for Notarization:**
+ 3. **Submit for Notarization** using a keychain profile created once with `notarytool store-credentials`:
```bash
- xcrun notarytool submit App.zip \
- --apple-id "your@email.com" \
- --team-id "TEAMID" \
- --password "@keychain:AC_PASSWORD" \
- --wait
+ xcrun notarytool submit App.zip --keychain-profile "notarytool-password" --wait
```
- 4. **Staple Ticket:**
+ 4. **Staple the ticket to the app, then re-package.** "While you can notarize a ZIP archive, you can't staple to it directly. Instead, run `stapler` against each item that you added to the archive. Then create a new ZIP file containing the stapled items for distribution."[^notarization-workflow]
```bash
xcrun stapler staple App.app
xcrun stapler validate App.app
+ ditto -c -k --keepParent App.app App.zip # the distributed archive must contain the stapled app
```
- **Critical Changes:**
- - macOS Sequoia: Even stricter Gatekeeper enforcement
- - **notarytool** replaced legacy **altool**
- - Sign bottom-up in bundle hierarchy (frameworks first, then app)
- - **Never use --deep**—sign each component individually
+ **Rules:**
+ - Sign bottom-up in the bundle hierarchy; do not sign with `--deep` (`man codesign` marks it "DEPRECATED for signing as of macOS 13.0"), because it applies the outer entitlements and flags to nested code. `--deep` remains the right flag for `--verify`.
+ - `notarytool` replaced `altool`, which Apple stopped accepting on November 1, 2023; the `@keychain:` password syntax was `altool`'s, and `notarytool` uses `--keychain-profile` or a literal `--password`.
+ - macOS Sequoia removed the Control-click override for Gatekeeper; users must approve unsigned or un-notarized software in System Settings, so notarize anything distributed outside the App Store.[^sequoia-gatekeeper]
**Common Failures:**
- "Hardened runtime not enabled" → Add `--options runtime`
- "Invalid signature" → Re-sign with proper entitlements
</code_signing>
## Architecture Patterns
<architecture_patterns>
**For general architecture philosophy, see the `software-engineer` skill.** This section covers macOS-specific architectural considerations.
**Primary Patterns (Choose One):**
### MVC (Model-View-Controller)
**What it is:**
- Apple's classic pattern for AppKit development
- Model: Data and business logic
- View: UI components
- Controller: Coordinates between Model and View
**When to use:**
- AppKit-heavy applications
- Document-based apps (works naturally with NSDocument)
- Simple-to-medium complexity apps
- When following Apple's conventions makes sense
**Reality check:**
- Controllers tend to become large ("Massive View Controller")
- This is fine for many apps—just watch for controller bloat
- - When controllers exceed ~300-400 lines, consider extracting logic
+ - Treat a controller that has grown to several hundred lines as a signal to extract logic; the number is a rule of thumb, not a threshold
### MV (Model-View)
**What it is:**
- - Apple's recommended pattern for simple SwiftUI apps
+ - The pattern the cited author distills from Apple's SwiftUI sample code: no separate view-model layer[^mv-pattern]
- Model: Data and business logic
- View: SwiftUI views with @State for local state
- No separate ViewModel layer - views call model methods directly
**When to use:**
- Simple-to-medium SwiftUI applications
- Prototypes and MVPs
- Apps without complex testability requirements
- When MVVM feels like overkill
**Reality check:**
- SwiftUI's reactive binding means views often serve as their own view models[^mv-pattern]
- - Minimal boilerplate, fastest development velocity
- - Works for most SwiftUI apps until models exceed ~500 lines
- - When models get large, extract logic or move to MVVM
+ - Minimal boilerplate
+ - Works until a model accumulates enough presentation logic that views become hard to test; then extract logic or move to MVVM
**Pattern:**
```swift
+ @MainActor
@Observable
class User {
var name: String = ""
var email: String = ""
func save() {
// Business logic here
}
}
struct ProfileView: View {
@State private var user = User()
var body: some View {
Form {
TextField("Name", text: $user.name)
Button("Save") { user.save() }
}
}
}
```
### MVVM (Model-View-ViewModel)
**What it is:**
- Model: Data and business logic
- View: SwiftUI views or AppKit views
- ViewModel: Presentation logic, formats data for View
**When to use:**
- SwiftUI applications needing testable presentation logic
- Multiple views display same data differently
- Need to separate view logic from view definition
- Models have grown too large in MV pattern
**Reality check:**
- Works beautifully with SwiftUI's reactive nature
- Less natural in pure AppKit (but still usable)
- ViewModels can also bloat—same solution as MVC (extract logic)
- Consider if you actually need it vs simpler MV pattern
### VIPER (View-Interactor-Presenter-Entity-Router)
**What it is:**
- Ultra-granular pattern splitting each screen into 5+ components
- View: Displays data
- Interactor: Business logic
- Presenter: Formats data for view
- Entity: Data models
- Router: Navigation
**When to use:**
- Rarely. Honest assessment: generally over-engineered
- Large enterprise apps with extreme testability requirements
- Teams that need architectural enforcement of separation
- - **AppKit only** - actively fights SwiftUI's design
+ - Fits AppKit; in SwiftUI its Router layer duplicates state-driven navigation, and its Presenter's formatting can live in a view model or the model without a separate layer
**Reality check:**
- Massive boilerplate (5+ files per screen)
- Most sources say "only if you really need it"
- - **In SwiftUI:** Presenter layer redundant (auto-updates), Router obsolete (state-driven navigation)
+ - **In SwiftUI:** observation propagates model changes to views, so the Presenter's update plumbing disappears (its formatting logic moves into a view model or the model); the Router's routing becomes navigation state (`NavigationStack` with a `NavigationPath` held in an observable model), with navigation policy still yours to write
- **Don't force this pattern into SwiftUI** - you'll fight the framework constantly
- Consider carefully whether the complexity is justified even in AppKit
**Complementary Pattern (Add When Needed):**
### Coordinator Pattern
**What it is:**
- Handles navigation and screen flow
- Works **on top of** MVC or MVVM (not instead of)
- Removes navigation logic from view controllers/view models
- Common combinations: MVVM-C, MVC-C
- **Primarily an AppKit/UIKit pattern**
**When to add Coordinator:**
- - Complex navigation in AppKit apps (10+ screens)
+ - Complex navigation in AppKit apps (many screens and flows)
- Deep linking (URLs open specific screens)
- Multiple entry points to same screen
- A/B testing different user flows
- Reusing view controllers in different contexts
**SwiftUI Reality:**
- **Don't force Coordinators into SwiftUI** - navigation is declarative and state-driven
- - SwiftUI uses NavigationStack, NavigationPath, @State, .sheet(), .fullScreenCover()
- - SwiftUI's view hierarchy is static at compile-time, not dynamically modifiable
- - Use SwiftUI's built-in navigation patterns instead
+ - SwiftUI navigates through state: `NavigationStack` and `NavigationSplitView` driven by a `NavigationPath` or selection value, `.sheet()`, and on macOS separate `Window`/`WindowGroup` scenes (`.fullScreenCover()` is not available on macOS)
+ - Navigation policy can live in an observable model that owns the path; that is the SwiftUI counterpart of a coordinator
**Pattern (AppKit/UIKit):**
```
App uses MVC or MVVM for view/logic organization
+
Coordinator manages navigation between screens
```
**Other Concerns:**
Patterns like Repository (data access), networking layers, and business logic extraction emerge on a case-by-case basis during actual project design. Don't prematurely abstract.
</architecture_patterns>
## Performance Optimization
<performance>
**Profiling with Instruments:**
Essential templates:
- - **Time Profiler:** CPU usage, call stacks, bottlenecks (use for 80% of profiling)
+ - **Time Profiler:** CPU usage, call stacks, bottlenecks; start here
- **Allocations:** Memory allocation patterns
- **Leaks:** Memory leak detection
- **Metal System Trace:** GPU performance
Best practices:
- Profile in Release mode (optimization critical)
- Profile on target hardware (older devices reveal issues)
- Use Signposts for precise measurement intervals
**Main Thread Optimization:**
- Main thread for UI updates and user input handling ONLY. Move off main thread:
- - Network requests
- - Data parsing
- - File I/O
+ Keep the main thread for UI updates and input handling. Blocking work moves off it; work that is already `async` and suspends (e.g., `URLSession` requests) can be initiated from the main actor without blocking it. Offload with `@concurrent` or a detached task (see `swift-programmer`):
+ - Data parsing and decoding of large responses
+ - Synchronous file I/O
- Complex calculations
- Image processing
**Layer-Backed View Optimization:**
- Layer-backed views have default redraw policy `NSViewLayerContentsRedrawDuringViewResize` which is "detrimental to animation performance" as it triggers the drawing step for each frame.[^layer-redraw]
+ A layer-backed view that draws with `draw(_:)` usually gets the redraw policy `NSViewLayerContentsRedrawDuringViewResize` (the SDK header: "Generally, the default value is NSViewLayerContentsRedrawOnSetNeedsDisplay if the view responds YES to -wantsUpdateLayer. Otherwise, the value is usually NSViewLayerContentsRedrawDuringViewResize"),[^nsview-header] which objc.io notes "might be detrimental to animation performance" because it triggers drawing on each frame of a resize.[^layer-redraw]
- Change to:
+ For views whose content doesn't depend on their size, change to:
```swift
view.layerContentsRedrawPolicy = .onSetNeedsDisplay
```
+ With this policy the view redraws only when you call `setNeedsDisplay`, so invalidation becomes your responsibility; content that depends on the view's size must invalidate on resize (or keep the default policy).
**When to Enable Layer-Backing:**
- Animating multiple views simultaneously
- Need smooth 60fps animations
- Want Core Animation features
- Creating effects requiring GPU acceleration
- **Avoid Layer-Backing When:**
- - Simple static UI with no animations
- - Memory extremely constrained (each layer has own backing store)
- - Need precise pixel-level drawing control
+ **Leave Layer-Backing Off When:**
+ - Static UI with no animation, where it buys nothing
+ - Memory is constrained; layers carry backing stores, though AppKit coalesces content where it can
+ - The view relies on precise, resolution-aware drawing that you have verified renders differently when backed
</performance>
## Testing Strategy
<testing>
**For general testing philosophy and TDD principles, see the `test-driven-development` skill.**
- **Swift Testing vs XCTest (2024-2025):**
+ **Swift Testing vs XCTest:**
Swift Testing (Xcode 16+):
- Modern replacement using macros (`@Test`, `#expect`, `#require`)
- Better parallelization (in-process using Swift Concurrency)
- Works with structs/actors/classes, not just XCTestCase
- Use for: New unit tests in Swift 6/Xcode 16+ projects
XCTest:
- - Required for UI automation (XCUIApplication)
- - Required for performance tests (XCTMetric)
- - Necessary for Objective-C codebases
+ - Apple's guidance: "continue using XCTest for any tests which use UI automation APIs like XCUIApplication or use performance testing APIs like XCTMetric as these are not supported in Swift Testing"[^wwdc24-10179]
+ - Necessary for Objective-C test code
- Use for: UI automation tests, performance tests, existing test suites
- **Recommended Distribution:**
- 1. **Unit Tests (70%):** View models, business logic, data models
- 2. **Integration Tests (20%):** Component interactions, API clients
- 3. **UI Tests (10%):** Critical user flows using XCTest
+ **Distribution:** put most coverage in unit tests of models and logic, integration tests where components meet (e.g., API clients), and UI tests only for the flows whose breakage would ship a broken app, because UI tests are the slowest and most brittle tier.
**Accessibility Testing:**
VoiceOver on macOS differs from iOS:
- Keyboard navigation primary (not touch)
- VoiceOver Utility for configuration
- Menu bar accessibility critical
- Multiple windows/spaces support
Testing workflow:
- Start VoiceOver: ⌘ + F5
- Navigate with Control + Option (VO keys)
- - Verify all interactive elements accessible
- - Test with Safari (best VoiceOver support)
+ - Verify all interactive elements are reachable and labeled (use the Accessibility Inspector in Xcode to audit)
</testing>
## Common Anti-Patterns
<anti_patterns>
### iOS Patterns Applied Incorrectly
- Assuming layer-backing is automatic
- Using UIKit coordinate conventions without override
- Treating NSWindow like UIWindow (passive container)
- Ignoring window management patterns
- Not implementing hover states (mouse tracking)
- Missing right-click context menus
### Web Developer Mistakes
- - Expecting CSS-like layout flexibility (use Auto Layout constraints)
+ - Expecting CSS-like layout flexibility (AppKit lays out with Auto Layout constraints; SwiftUI with its layout containers and modifiers)
- Fighting native controls instead of embracing system appearance
- Ignoring accessibility (VoiceOver is expected, not optional)
- - Single-window mentality (macOS users expect multi-window support)
+ - Single-window mentality where users expect multiple windows (documents, inspectors, palettes); a single-window app is fine when its task is single-window
- Not using native file dialogs (NSOpenPanel/NSSavePanel)
- Ignoring keyboard shortcuts and menu bar conventions
### Linux/Cross-Platform Developer Mistakes
- Assuming POSIX conventions apply to GUI (Cocoa is not GTK/Qt)
- Fighting sandboxing instead of designing around it
- Ignoring Apple's signing/notarization requirements
- - Using raw file paths instead of security-scoped bookmarks
+ - Persisting raw paths to files outside the container instead of security-scoped bookmarks (container-local paths need no bookmark)
- Not adapting to macOS HIG (menu bar, dock, system preferences)
### Windows Developer Mistakes
- Expecting registry-like global preferences (use UserDefaults, sandboxed)
- Assuming all file access is available (sandbox constraints)
- - Window chrome expectations (macOS uses unified toolbars, not separate title bars)
- - Installation expectations (drag-to-Applications, no installer wizards)
+ - Window chrome expectations (unified title-and-toolbar is the macOS norm, though separate title bars remain available)
+ - Installation expectations (drag-to-Applications is the norm for apps; installer packages exist for software that needs privileged installation)
**Over-Engineering Simple Apps:**
- Using VIPER for simple CRUD apps
- Complex architectural patterns for prototypes
- - Custom state management when @EnvironmentObject suffices
+ - Custom state management when an `@Observable` model in the environment suffices
- Premature abstraction before requirements are clear
**Ignoring macOS UI Conventions:**
- Not following macOS Human Interface Guidelines
- Poor keyboard shortcut support
- Ignoring standard menu bar conventions
- Misusing window management (minimize, zoom, full screen)
- Not respecting macOS-specific UI elements (toolbars, sidebars, split views)
### Premature SwiftUI Adoption
- - Attempting 100% SwiftUI for complex Mac apps
+ - Committing to 100% SwiftUI before checking that the app's window, text, and event needs are within SwiftUI's current surface (see `<swiftui_limitations>`)
- Ignoring AppKit when SwiftUI is insufficient
- - Not budgeting for 20-30% AppKit fallback code
+ - Not budgeting for AppKit fallback code
- Assuming iOS SwiftUI code will work on macOS
</anti_patterns>
## Mac App Store vs Direct Distribution
<distribution>
**Mac App Store:**
- **Pros:** Apple handles distribution/updates, user trust, search visibility
- - **Cons:** REQUIRED full sandboxing, 30% commission, review delays, limited APIs
+ - **Cons:** App Sandbox required; commission of 30%, reduced to 15% for developers in the App Store Small Business Program (developers with up to US$1 million in prior-year proceeds)[^small-business] and for auto-renewable subscriptions after a subscriber's first year;[^subscriptions] review delays; limited APIs
**Direct Distribution:**
- **Pros:** Greater API access, no commission, faster updates, custom pricing
- - **Cons:** Must handle payments, notarization required, no built-in discoverability
+ - **Cons:** Must handle payments; Developer ID signing and notarization required for Gatekeeper to allow the app without the user granting an exception in System Settings; no built-in discoverability
- **Recommendation:** Professional tools choose direct distribution for flexibility; consumer apps often choose MAS for trust and distribution.
+ **Tendency, not rule:** tools that need APIs the sandbox forbids, or their own licensing, go direct; apps that benefit from App Store discovery and trust go there. Many ship both.
</distribution>
## Decision Frameworks Summary
<decision_summary>
**SwiftUI vs AppKit:**
- Simple-medium apps targeting macOS 14+: SwiftUI
- - Complex text editing, large datasets, custom windows: AppKit
- - Production apps where bugs = revenue loss: AppKit
+ - Text editing beyond `TextEditor`'s surface, data sets where profiling shows `List` behind `NSTableView`, window behavior SwiftUI doesn't expose: AppKit for that part
+ - Behavior that must not change between OS releases: prefer the framework whose implementation of that behavior has been stable, which for windows, text, and events has usually been AppKit
- Professional tools: Hybrid (AppKit foundation, SwiftUI where appropriate)
**Architecture Pattern:**
- AppKit apps: MVC
- Simple SwiftUI apps: MV (Model-View)
- SwiftUI apps needing testability: MVVM
- - Complex AppKit navigation (10+ screens, deep linking): Add Coordinator (don't use in SwiftUI)
- - Rarely: VIPER for AppKit only (if you genuinely need extreme architectural enforcement)
+ - Complex AppKit navigation (many screens, deep linking): Add a Coordinator; in SwiftUI, hold navigation state in an observable model instead
+ - Rarely: VIPER, and then in AppKit rather than SwiftUI (if you genuinely need that degree of separation)
**State Management:**
- View-local: @State
- - Simple global (1-2 objects): @EnvironmentObject
- - Medium-to-complex apps: @Observable singleton or multiple @Observable types
+ - Shared across views (macOS 14+): `@MainActor @Observable` models injected with `.environment(_:)` and read with `@Environment`; `@EnvironmentObject` pairs with the older `ObservableObject` protocol and is for code that still uses it
+ - Larger apps: several focused `@MainActor @Observable` types rather than one singleton
**Cross-Platform:**
- - Similar features, 70%+ overlap: SwiftUI multiplatform
- - Premium Mac experience: Separate codebases with shared business logic
+ - Substantial feature overlap with acceptable compromises: SwiftUI multiplatform
+ - Mac-specific interaction (multi-window, menus, keyboard) that a shared UI would flatten: separate platform UIs over shared business logic
- Complex apps: Hybrid (shared Swift Package for logic, platform UIs)
</decision_summary>
+ ## Recent Changes
+
+ <recent_changes>
+ **Baseline: February 2025.** This section assumes trained knowledge of macOS development through that date — the reliable-knowledge cutoff of Claude Haiku 4.5, the oldest among the current Claude models[^claude-models] — and lists what has changed since that bears on this skill's guidance. Treat anything older (macOS 15 and Xcode 16 included) as known.
+
+ **Additions**, by release:
+ - **macOS 26 / Xcode 26** (September 2025): the Liquid Glass design, with `NSGlassEffectView` and `NSGlassEffectContainerView` in AppKit and matching SwiftUI APIs;[^liquid-glass-guide] styled text editing in SwiftUI with `AttributedString`,[^styled-text-guide] and Find Bar control in `TextEditor` (`findNavigator(isPresented:)`);[^macos26-notes] a SwiftUI `WebView` backed by an observable `WebPage`;[^webkit-guide] animated `Window` resizing from a SwiftUI transaction;[^macos26-notes] `NavigationLink` producing a single view in lazy containers, improving `List` performance;[^macos26-notes] an "Enhanced Security" helper-extension template for isolating untrusted-data handling.[^xcode26-notes] These move the SwiftUI/AppKit boundary in `<swiftui_vs_appkit_decision>`.
+ - **Xcode 27** (beta 6 as of September 2026): `@State` becomes a macro with lazy initial-value evaluation; see the gotcha in `<swiftui_appkit_integration>` and TN3211.[^tn3211]
+
+ **Behavior changes, deprecations, and things being moved away from:**
+ - **SceneKit is deprecated** "across all Apple platforms"; Apple recommends RealityKit for new projects.[^xcode26-notes]
+ - **`Text` concatenation with `+` is deprecated** in favor of `Text` interpolation, for localization correctness.[^macos26-notes]
+ - **Text writing direction** in `Text`, `TextEditor`, and `TextField` is now derived per paragraph from string content rather than layout direction on macOS 26.[^macos26-notes]
+ - **Instruments' SwiftUI template** replaced the View Body and View Properties instruments, which are deprecated but still available.[^xcode26-notes]
+ - **New app projects default to main-actor isolation** in Xcode 26; see `swift-programmer` for the concurrency consequences.
+ </recent_changes>
+
## Resources
<resources>
**Local Documentation:**
- Xcode diagnostic docs: `/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/share/doc/swift/diagnostics/`
- - LLM-optimized guides: `/Applications/Xcode.app/Contents/PlugIns/IDEIntelligenceChat.framework/Versions/A/Resources/AdditionalDocumentation/`
+ - Framework guides bundled with Xcode: `/Applications/Xcode.app/Contents/PlugIns/IDEIntelligenceChat.framework/Versions/A/Resources/AdditionalDocumentation/` (e.g., `AppKit-Implementing-Liquid-Glass-Design.md`, `SwiftUI-Styled-Text-Editing.md`, `SwiftUI-WebKit-Integration.md`)
**Apple Official:**
- AppKit Documentation: https://developer.apple.com/documentation/appkit
- macOS HIG: https://developer.apple.com/design/human-interface-guidelines/macos
- SwiftUI Documentation: https://developer.apple.com/documentation/swiftui
**Expert Blogs:**
- NSHipster: https://nshipster.com (Overlooked Cocoa/Swift bits)
- objc.io: https://www.objc.io (Advanced techniques, essential "AppKit for UIKit Developers" article)
- Mike Ash Friday Q&A: https://www.mikeash.com/pyblog/ (Deep low-level technical articles)
- Brent Simmons Inessential: https://inessential.com (Veteran Mac developer, NetNewsWire author)
- Use Your Loaf: https://useyourloaf.com (Practical guides, WWDC viewing guides)
**Books:**
- - "macOS by Tutorials" (v3.0) by TrozWare - Updated for macOS 15 & Xcode 16
- - "Hacking with macOS" by Paul Hudson - 18 projects, free online
+ - "macOS Apps Step by Step" (formerly "macOS by Tutorials"; v4.0, November 2025) by Sarah Reichelt (TrozWare) - covers macOS 26 and Xcode 26
+ - "Hacking with macOS" by Paul Hudson - 18 projects; paid, with a free sample
- "The Complete Friday Q&A" (Volumes I-III) by Mike Ash - Essential for Cocoa internals
**Open Source Examples:**
- Awesome Open Source Mac Apps: https://github.com/serhii-londar/open-source-mac-os-apps
- - NetNewsWire: https://github.com/brentsimmons (Mature AppKit codebase)
+ - NetNewsWire: https://github.com/Ranchero-Software/NetNewsWire (Mature AppKit codebase)
- WWDC app (unofficial): https://github.com/insidegui/WWDC (Well-structured Mac app)
**Community:**
- Apple Developer Forums: https://developer.apple.com/forums/
- Stack Overflow (cocoa/appkit tags)
</resources>
## The Modern macOS Developer's Mindset
- **Embrace Hybrid Solutions:** The best Mac apps in 2025 blend SwiftUI and AppKit strategically. Use each framework where it excels. Don't force SwiftUI where AppKit is superior.
+ **Embrace Hybrid Solutions:** Well-regarded Mac apps blend SwiftUI and AppKit. Use each framework where it excels. Don't force SwiftUI where AppKit is superior.
**Leverage Platform Strengths:** macOS isn't iOS with a bigger screen. Multiple windows, menu bars, keyboard navigation, document architecture—these are first-class citizens. iOS developers transitioning to Mac: unlearn iOS assumptions.
- **Test Relentlessly:** Swift Testing for unit tests, XCTest for UI automation. 70% unit, 20% integration, 10% UI. Profile with Instruments regularly. VoiceOver test every release.
+ **Test Relentlessly:** Swift Testing for unit tests, XCTest for UI automation. Profile with Instruments regularly. VoiceOver-test every release.
**Stay Pragmatic:** The perfect architecture that ships beats the theoretical ideal that doesn't. Balance theoretical purity with practical delivery. Ship working software, iterate based on real problems, refactor when justified by pain.
**Final Wisdom:** The best Mac apps feel like Mac apps. They embrace platform conventions, leverage macOS strengths, and don't feel like iPad apps in disguise. Build for Mac users, not iOS users with keyboards. Master the frameworks, respect the platform, ship great software.
## Sources
<sources>
- [^ghostty-devlog]: Mitchell Hashimoto. 2024. Ghostty Devlog 002. https://mitchellh.com/writing/ghostty-devlog-002
+ [^ghostty-devlog]: Mitchell Hashimoto. 2023. Ghostty Devlog 002 (August 5, 2023). Retrieved September 5, 2026 from https://mitchellh.com/writing/ghostty-devlog-002
- [^multi-swiftui]: Multi.app. 2023. Moving to SwiftUI from macOS Cocoa. https://multi.app/blog/moving-to-swiftui-from-macos-cocoa-or-ios-cocoa-touch
+ [^multi-swiftui]: Multi.app. 2023. Moving to SwiftUI from macOS Cocoa (April 5, 2023). Retrieved September 5, 2026 from https://multi.app/blog/moving-to-swiftui-from-macos-cocoa-or-ios-cocoa-touch
[^mv-pattern]: Mohammad Azam. 2022. SwiftUI Architecture — A Complete Guide to the MV Pattern Approach. https://betterprogramming.pub/swiftui-architecture-a-complete-guide-to-mv-pattern-approach-5f411eaaaf9e
[^layer-redraw]: objc.io. AppKit for UIKit Developers. Issue #14. https://www.objc.io/issues/14-mac/appkit-for-uikit-developers
+
+ [^swiftui-state]: Apple Inc. State. SwiftUI Documentation. Retrieved September 5, 2026 from https://developer.apple.com/documentation/swiftui/state
+
+ [^wwdc22-10090]: Apple Inc. 2022. What's new in TextKit and text views (WWDC22 session 10090), session transcript. Retrieved September 5, 2026 from https://developer.apple.com/videos/play/wwdc2022/10090/
+
+ [^released-when-closed]: Apple Inc. isReleasedWhenClosed. AppKit Documentation. Retrieved September 5, 2026 from https://developer.apple.com/documentation/appkit/nswindow/isreleasedwhenclosed
+
+ [^subscriptions]: Apple Inc. Offer auto-renewable subscriptions. App Store Connect Help. Retrieved September 5, 2026 from https://developer.apple.com/help/app-store-connect/manage-subscriptions/offer-auto-renewable-subscriptions/
+
+ [^stylemask-borderless]: Apple Inc. NSWindow.StyleMask.borderless. AppKit Documentation. Retrieved September 5, 2026 from https://developer.apple.com/documentation/appkit/nswindow/stylemask-swift.struct/borderless
+
+ [^stylemask]: Apple Inc. styleMask. AppKit Documentation. Retrieved September 5, 2026 from https://developer.apple.com/documentation/appkit/nswindow/stylemask-swift.property
+
+ [^stylemask-fullsize]: Apple Inc. NSWindow.StyleMask.fullSizeContentView. AppKit Documentation. Retrieved September 5, 2026 from https://developer.apple.com/documentation/appkit/nswindow/stylemask-swift.struct/fullsizecontentview
+
+ [^nsview-header]: Apple Inc. `NSView.h`, comment on `layerContentsRedrawPolicy`. macOS 26.5 SDK, `System/Library/Frameworks/AppKit.framework/Headers/NSView.h`.
+
+ [^appkit-rn-1010]: Apple Inc. AppKit Release Notes for OS X 10.10 (older notes), "NSViewController" section. Retrieved September 5, 2026 from https://developer.apple.com/library/archive/releasenotes/AppKit/RN-AppKitOlderNotes/
+
+ [^textkit-compat]: Apple Inc. NSTextView, overview. AppKit Documentation. Retrieved September 5, 2026 from https://developer.apple.com/documentation/appkit/nstextview
+
+ [^appkit-rn-window]: Apple Inc. AppKit Release Notes for macOS 10.13, "NSWindow Lifecycle Changes." Retrieved September 5, 2026 from https://developer.apple.com/library/archive/releasenotes/AppKit/RN-AppKit/
+
+ [^collection-behavior]: Apple Inc. NSWindow.CollectionBehavior.moveToActiveSpace. AppKit Documentation. Retrieved September 5, 2026 from https://developer.apple.com/documentation/appkit/nswindow/collectionbehavior-swift.struct/movetoactivespace
+
+ [^event-architecture]: Apple Inc. Event Architecture, "Action Messages" (responder chain of a document-based application). Cocoa Event Handling Guide (archived). Retrieved September 5, 2026 from https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/EventOverview/EventArchitecture/EventArchitecture.html
+
+ [^security-scoped]: Apple Inc. startAccessingSecurityScopedResource(). Foundation Documentation. Retrieved September 5, 2026 from https://developer.apple.com/documentation/foundation/url/startaccessingsecurityscopedresource()
+
+ [^notarization-workflow]: Apple Inc. Customizing the notarization workflow. Security Documentation. Retrieved September 5, 2026 from https://developer.apple.com/documentation/security/customizing-the-notarization-workflow
+
+ [^sequoia-gatekeeper]: Apple Inc. 2024. Updates to runtime protection in macOS Sequoia (August 6, 2024). Apple Developer News. Retrieved September 5, 2026 from https://developer.apple.com/news/?id=saqachfa
+
+ [^wwdc24-10179]: Apple Inc. 2024. Meet Swift Testing (WWDC24 session 10179), session transcript. Retrieved September 5, 2026 from https://developer.apple.com/videos/play/wwdc2024/10179/
+
+ [^small-business]: Apple Inc. App Store Small Business Program. Retrieved September 5, 2026 from https://developer.apple.com/app-store/small-business-program/
+
+ [^macos26-notes]: Apple Inc. 2025. macOS 26 Release Notes, SwiftUI and TextKit sections. Retrieved September 5, 2026 from https://developer.apple.com/documentation/macos-release-notes/macos-26-release-notes
+
+ [^xcode26-notes]: Apple Inc. 2025. Xcode 26 Release Notes (SceneKit deprecation 147454720; Instruments deprecations 148596828; Enhanced Security extension template 141308470). Retrieved September 5, 2026 from https://developer.apple.com/documentation/xcode-release-notes/xcode-26-release-notes
+
+ [^liquid-glass-guide]: Apple Inc. 2025. Implementing Liquid Glass Design in AppKit. Bundled with Xcode 26 at `/Applications/Xcode.app/Contents/PlugIns/IDEIntelligenceChat.framework/Versions/A/Resources/AdditionalDocumentation/AppKit-Implementing-Liquid-Glass-Design.md`
+
+ [^styled-text-guide]: Apple Inc. 2025. Styled Text Editing in SwiftUI. Bundled with Xcode 26 at `.../AdditionalDocumentation/SwiftUI-Styled-Text-Editing.md`
+
+ [^webkit-guide]: Apple Inc. 2025. SwiftUI WebKit Integration. Bundled with Xcode 26 at `.../AdditionalDocumentation/SwiftUI-WebKit-Integration.md`
+
+ [^claude-models]: Anthropic. 2026. Models overview, "Compare models" table, row "Reliable knowledge cutoff." Claude API Documentation. Retrieved September 5, 2026 from https://platform.claude.com/docs/en/models/overview
+
+ [^tn3211]: Apple Inc. 2026. TN3211: Resolving SwiftUI source incompatibilities for State and ContentBuilder, section "How @State evolved to support laziness." Apple Technotes. Retrieved September 5, 2026 from https://developer.apple.com/documentation/technotes/tn3211-resolving-swiftui-source-incompatibilities-for-state-and-contentbuilder
</sources>