# AXPref Macro System Documentation

## 1. Introduction & Overview

### What is AXPref?

The AXPref macro system is a modern, Swift-first approach to application settings and preferences management. It provides a declarative way to define settings that are type-safe, observable, and automatically synchronized across your application.

### Key Benefits

**Type Safety**: Settings are strongly typed at compile time, eliminating common runtime errors from incorrect preference keys or type mismatches.

**Automatic Observability**: Built on Swift's Observation framework, settings automatically notify observers when values change, integrating seamlessly with SwiftUI and modern Swift concurrency.

**Intelligent Caching**: The system includes sophisticated caching mechanisms that reduce preference store access while maintaining consistency across your application.

**Streaming Support**: Settings provide `AsyncStream` interfaces for real-time updates, perfect for reactive programming patterns.

**Cross-Platform Sync**: Built-in support for syncing settings across devices, including watchOS companion apps.

**Rich Type Support**: Leverages Swift's Codable protocol with `AXMigratingEncoder/Decoder` to seamlessly store complex structs, enums, and custom types while maintaining human-readable values in the underlying preference store. This enables you to use sophisticated data models for your settings while keeping the stored preferences inspectable and debuggable.

### Architecture Overview

The AXPref system consists of several key components:

- **`@AXPref` macro**: Transforms property declarations into full-featured setting records
- **`AXSettingRecord<T>`**: The core observable setting implementation with caching and streaming
- **`AXSettingsStore` protocol**: Pluggable storage backend abstraction
- **`AXMigratingEncoder/Decoder`**: Handles encoding/decoding of complex types to readable preference values
- **Attributes system**: SwiftUI-inspired metadata for extensible setting configuration
- **Runtime introspection**: Query and enumerate settings by traits and metadata

### Basic Example

```swift
@AXSettingsGroup(domain: "com.myapp.settings")
class AppSettings {
    @AXPref
    var themeMode: ThemeMode = .automatic
    
    @AXPref(traits: [.watchSync])
    var notificationsEnabled: Bool = true
    
    @AXPref
    var userProfile: UserProfile = UserProfile(name: "Default", preferences: .standard)
}
```

This simple declaration creates fully-featured settings that are:
- Automatically persisted to your chosen storage backend
- Observable for UI binding and reactive programming  
- Cached for optimal performance
- Available as async streams for real-time updates
- Encoded as readable values in preferences (even complex custom types)

## 2. Basic Usage

### Simple Setting Declarations

The `@AXPref` macro transforms property declarations into fully-featured settings. By default, keys are automatically generated from your property names:

```swift
@AXSettingsGroup(domain: "com.myapp.settings")
class AppSettings {
    @AXPref
    var userName: String = "Guest"
    // Key: "AppSettingsUserName"
    
    @AXPref
    var maxItems: Int = 50
    // Key: "AppSettingsMaxItems"
    
    @AXPref
    var debugMode: Bool = false
    // Key: "AppSettingsDebugMode"
}
```

### Supported Types

AXPref supports three categories of types out of the box:

**Basic Codable Types**: Any type conforming to `Codable` can be stored directly:
```swift
@AXPref
var lastSyncDate: Date = Date()

@AXPref
var userSettings: UserConfiguration = UserConfiguration.default
```

**RawRepresentable Types**: Enums and other `RawRepresentable` types with `Codable` raw values:
```swift
enum Theme: String, Codable {
    case light, dark, automatic
}

@AXPref
var theme: Theme = .automatic
```

**RawRepresentable + Codable Types**: Types that are both `RawRepresentable` and `Codable` for maximum flexibility:
```swift
enum Priority: Int, Codable {
    case low = 0, medium = 1, high = 2
}

@AXPref
var defaultPriority: Priority = .medium
```

### Key Generation and Prefixes

The `@AXSettingsGroup` macro supports automatic key generation and prefixing:

**Default behavior**: Keys are generated by combining the class name with the property name in pascal case:
```swift
@AXSettingsGroup(domain: "com.myapp.settings")
class AppSettings {
    @AXPref
    var timeoutInterval: TimeInterval = 30.0
    // Uses "AppSettingsTimeoutInterval" as the key
}
```

**Customizing the key prefix**: Use the `keyPrefix` parameter to override the default class name prefix:
```swift
@AXSettingsGroup(domain: "com.myapp.ui", keyPrefix: "UI")
class InterfaceSettings {
    @AXPref
    var animationDuration: Double = 0.3
    // Uses "UIAnimationDuration" as the key
    
    @AXPref
    var reducedMotion: Bool = false
    // Uses "UIReducedMotion" as the key
}
```

**Overriding with explicit keys**: When you need specific key names, use the `key` parameter:
```swift
@AXSettingsGroup(domain: "com.myapp.settings")
class AppSettings {
    @AXPref
    var timeoutInterval: TimeInterval = 30.0
    // Uses generated key: "AppSettingsTimeoutInterval"
    
    @AXPref(key: "legacy_timeout_setting")
    var legacyTimeout: Int = 60
    // Uses explicit key: "legacy_timeout_setting"
}
```

### Multiple Settings Groups

You can organize settings into multiple groups with different domains and key prefixes:

```swift
@AXSettingsGroup(domain: "com.myapp.network", keyPrefix: "Network")
class NetworkSettings {
    @AXPref
    var timeoutSeconds: Int = 30
    // Key: "NetworkTimeoutSeconds"
    
    @AXPref
    var retryCount: Int = 3
    // Key: "NetworkRetryCount"
}

@AXSettingsGroup(domain: "com.myapp.ui", keyPrefix: "Interface")  
class UISettings {
    @AXPref
    var animationSpeed: Double = 1.0
    // Key: "InterfaceAnimationSpeed"
    
    @AXPref
    var showDebugInfo: Bool = false
    // Key: "InterfaceShowDebugInfo"
}
```

### Working with Settings

Once declared, settings behave like normal Swift properties:

```swift
let networkSettings = NetworkSettings()

// Read values
print("Timeout: \(networkSettings.timeoutSeconds)")

// Write values (automatically persists and notifies observers)
networkSettings.timeoutSeconds = 60

// Use in SwiftUI (automatic UI updates when settings change)
struct SettingsView: View {
    @State private var settings = UISettings()
    
    var body: some View {
        VStack {
            Slider(value: $settings.animationSpeed, in: 0.1...3.0)
            Toggle("Show Debug Info", isOn: $settings.showDebugInfo)
        }
    }
}
```

## 3. AXSettingsStore Protocol

### Purpose and Abstraction

The `AXSettingsStore` protocol provides a pluggable abstraction for where and how your settings are actually stored. This design allows you to:

- Switch storage backends without changing your settings declarations
- Use different stores for different types of settings
- Create mock stores for testing
- Implement custom storage solutions (cloud sync, encrypted storage, etc.)

### Settings Groups as Interface Definitions

A key architectural principle of the AXPref system is that your settings group classes define the **shape and interface** of your settings while completely abstracting away **where** they are actually stored. This separation of concerns enables powerful patterns:

```swift
@AXSettingsGroup(domain: "com.myapp.accessibility")
class AccessibilitySettings {
    @AXPref
    var voiceOverEnabled: Bool = false
    
    @AXPref
    var speechRate: Double = 0.5
    
    @AXPref
    var soundEffectsEnabled: Bool = true
    
    @AXPref
    var announceNotifications: Bool = true
}
```

This single class definition can be used across multiple contexts with different storage backends:

**Main Settings App**: Uses the standard Core Foundation preferences store
```swift
// In your main settings app
let settings = AccessibilitySettings()
// Uses AXCoreSettingsStore.shared by default
```

**Watch Bridge App**: Uses the NanoPreferences store to sync to the paired watch
```swift
// In your watch companion/bridge app
let watchSettings = AXPreferenceContext.$store.withValue(NPSettingsStore()) {
    AccessibilitySettings()
}
// Same interface, but writes to the paired watch
```

**Shared UI Components**: The same UI can work with both contexts
```swift
struct AccessibilitySettingsView: View {
    let settings: AccessibilitySettings
    
    var body: some View {
        Form {
            Toggle("VoiceOver", isOn: $settings.voiceOverEnabled)
            
            VStack(alignment: .leading) {
                Text("Speech Rate")
                Slider(value: $settings.speechRate, in: 0.1...1.0)
            }
            
            Toggle("Sound Effects", isOn: $settings.soundEffectsEnabled)
            Toggle("Announce Notifications", isOn: $settings.announceNotifications)
        }
    }
}

// Use the same UI in different apps:

// Main settings app
let mainAppView = AccessibilitySettingsView(settings: AccessibilitySettings())

// Watch bridge app  
let bridgeSettings = AXPreferenceContext.$store.withValue(NPSettingsStore()) {
    AccessibilitySettings()
}
let bridgeAppView = AccessibilitySettingsView(settings: bridgeSettings)
```

This pattern eliminates code duplication and ensures consistency between your main app settings and companion app settings interfaces.

### Protocol Overview

```swift
public protocol AXSettingsStore {
    func value(forKey key: AXSettingsKey) -> AnyHashable?
    func set(value: AnyHashable?, forKey key: AXSettingsKey)
    func observeWithToken(key: AXSettingsKey, onChange: @escaping () -> Void) -> UUID
    func removeObserver(token: UUID)
}
```

### Built-in Implementations

**AXCoreSettingsStore**: The primary implementation backed by Core Foundation preferences:
```swift
@AXSettingsGroup(domain: "com.myapp.settings")
class AppSettings {
    // Uses AXCoreSettingsStore by default via AXPreferenceContext
    @AXPref
    var userName: String = "Guest"
}
```

**NPSettingsStore**: Specialized store for watchOS synchronization. Used in bridge (watch companion) apps to write settings to the paired watch from the phone:
```swift
// In your watch companion app
@AXSettingsGroup(domain: "com.myapp.watch")
class WatchSyncSettings {
    @AXPref
    var notificationsEnabled: Bool = true
    
    @AXPref
    var workoutAutoStart: Bool = false
}
```

### Using Alternative Stores with AXPreferenceContext

The most flexible way to use alternative stores is through `AXPreferenceContext.$store.withValue()`, which temporarily overrides the store context during initialization:

#### Basic Alternative Store Usage

```swift
// Create settings with a custom store
let customStore = MyCustomSettingsStore()
let settingsWithCustomStore = AXPreferenceContext.$store.withValue(customStore) {
    AppSettings()
}
```

#### Watch Synchronization

```swift
// Configure settings for watch syncing
let watchSettings = AXPreferenceContext.$store.withValue(NPSettingsStore()) {
    WatchSyncSettings()
}
```

#### Testing with In-Memory Store

```swift
// Create settings that don't persist to disk (perfect for testing)
let testSettings = AXPreferenceContext.$store.withValue(AXInMemorySettingsStore()) {
    AppSettings()
}

// Or use the convenience method
let testSettings = AppSettings.testSettings()
```

#### Multiple Store Contexts

```swift
// Different settings groups can use different stores
let localSettings = AXPreferenceContext.$store.withValue(AXCoreSettingsStore.shared) {
    LocalAppSettings()
}

let cloudSettings = AXPreferenceContext.$store.withValue(CloudSyncSettingsStore()) {
    UserCloudSettings()
}

let watchSettings = AXPreferenceContext.$store.withValue(NPSettingsStore()) {
    WatchSyncSettings()
}
```

### Custom Storage Backends

You can implement your own storage backend for specialized needs:

```swift
class CloudSyncSettingsStore: AXSettingsStore {
    private var storage: [String: AnyHashable] = [:]
    private var observers: [UUID: (AXSettingsKey, () -> Void)] = [:]
    
    func value(forKey key: AXSettingsKey) -> AnyHashable? {
        let fullKey = "\(key.domain).\(key.key)"
        
        // Check local cache first
        if let cached = storage[fullKey] {
            return cached
        }
        
        // Fetch from cloud service
        let cloudValue = fetchFromCloud(key: fullKey)
        storage[fullKey] = cloudValue
        return cloudValue
    }
    
    func set(value: AnyHashable?, forKey key: AXSettingsKey) {
        let fullKey = "\(key.domain).\(key.key)"
        storage[fullKey] = value
        
        // Sync to cloud asynchronously
        Task {
            await syncToCloud(key: fullKey, value: value)
        }
        
        // Notify observers
        notifyObservers(for: key)
    }
    
    func observeWithToken(key: AXSettingsKey, onChange: @escaping () -> Void) -> UUID {
        let token = UUID()
        observers[token] = (key, onChange)
        return token
    }
    
    func removeObserver(token: UUID) {
        observers.removeValue(forKey: token)
    }
    
    private func notifyObservers(for key: AXSettingsKey) {
        for (_, (observedKey, callback)) in observers {
            if observedKey.domain == key.domain && observedKey.key == key.key {
                callback()
            }
        }
    }
    
    // Implementation details for cloud sync...
    private func fetchFromCloud(key: String) -> AnyHashable? { /* ... */ }
    private func syncToCloud(key: String, value: AnyHashable?) async { /* ... */ }
}
```

### Testing with Mock Stores

For unit testing, create a simple in-memory mock store:

```swift
class MockSettingsStore: AXSettingsStore {
    private var storage: [String: AnyHashable] = [:]
    private var observers: [UUID: () -> Void] = [:]
    
    func value(forKey key: AXSettingsKey) -> AnyHashable? {
        return storage["\(key.domain).\(key.key)"]
    }
    
    func set(value: AnyHashable?, forKey key: AXSettingsKey) {
        let fullKey = "\(key.domain).\(key.key)"
        storage[fullKey] = value
        
        // Notify all observers for simplicity
        observers.values.forEach { $0() }
    }
    
    func observeWithToken(key: AXSettingsKey, onChange: @escaping () -> Void) -> UUID {
        let token = UUID()
        observers[token] = onChange
        return token
    }
    
    func removeObserver(token: UUID) {
        observers.removeValue(forKey: token)
    }
    
    // Testing helpers
    func reset() {
        storage.removeAll()
        observers.removeAll()
    }
    
    func hasValue(forKey key: String, domain: String) -> Bool {
        return storage["\(domain).\(key)"] != nil
    }
}

// Use in tests
func testSettingsBehavior() {
    let mockStore = MockSettingsStore()
    let testSettings = AXPreferenceContext.$store.withValue(mockStore) {
        AppSettings()
    }
    
    // Test behavior
    testSettings.userName = "TestUser"
    XCTAssertTrue(mockStore.hasValue(forKey: "AppSettingsUserName", domain: "com.myapp.settings"))
    XCTAssertEqual(testSettings.userName, "TestUser")
}
```

### Convenience Methods

Some settings classes provide convenience methods for common store scenarios:

```swift
@AXSettingsGroup(domain: "com.myapp.settings")
class AppSettings {
    @AXPref
    var userName: String = "Guest"
    
    @AXPref
    var isFirstLaunch: Bool = true
}

// Standard instance (uses default store)
let settings = AppSettings()

// Testing instance (uses in-memory store)
let testSettings = AppSettings.testSettings()

// Custom store instance
let customStoreSettings = AppSettings.make(alternateStore: MyCustomStore())
```

## 4. Attributes System (SwiftUI-inspired)

### Understanding AXSettingsAttributes

The AXPref system uses a SwiftUI-inspired attributes mechanism that directly mirrors how `EnvironmentValues` work in SwiftUI. This provides extensible metadata for your settings, allowing you to declaratively configure behavior, appearance, and capabilities without cluttering the core setting declaration.

```swift
@AXSettingsGroup(domain: "com.myapp.settings")
class AppSettings {
    @AXPref(traits: [.watchSync, .buddyExclude])
    var notificationsEnabled: Bool = true
    
    @AXPref(traits: [.guestPassInclude])
    @AXPrefAttr(\.watchKey, AXPreferenceKeyPair(key: "watch_theme", domain: "com.myapp.watch"))
    var themePreference: Theme = .automatic
}
```

### Core Attributes

#### Traits
The `traits` parameter accepts an `AXSettingsTrait` option set that controls various behaviors:

```swift
@AXPref(traits: [.watchSync])
var syncToWatch: Bool = false

@AXPref(traits: [.buddyExclude]) 
var localOnlyPreference: String = "device-specific"

@AXPref(traits: [.guestPassInclude])
var guestAccessibleSetting: Bool = true

@AXPref(traits: [.managedAssets])
var managedResourcePath: String = ""
```

#### Watch Key Mapping
Use `@AXPrefAttr(\.watchKey, ...)` to map a setting to a different key/domain on the watch:

```swift
@AXPref(traits: [.watchSync])
@AXPrefAttr(\.watchKey, AXPreferenceKeyPair(key: "complications_enabled", domain: "com.myapp.watchface"))
var showComplications: Bool = true
```

### Creating Custom Attributes (EnvironmentValues Pattern)

Custom attributes follow the exact same pattern as SwiftUI's `EnvironmentValues`. You create an attribute key type and extend `AXSettingsAttributes` with a computed property:

#### Step 1: Define the Attribute Key
```swift
// Private key type - follows the same pattern as SwiftUI EnvironmentKeys
private struct UISection: AXSettingsAttributeKey {
    static var defaultValue: UISectionValue? { nil }
}

private struct ValidationRule: AXSettingsAttributeKey {
    static var defaultValue: ValidationRuleValue? { nil }
}
```

#### Step 2: Define the Attribute Value Types
```swift
public struct UISectionValue {
    public let title: String
    public let icon: String
    
    public init(title: String, icon: String = "gear") {
        self.title = title
        self.icon = icon
    }
}

public struct ValidationRuleValue {
    public let minValue: Double
    public let maxValue: Double
    public let errorMessage: String
    
    public init(min: Double, max: Double, error: String = "Value out of range") {
        self.minValue = min
        self.maxValue = max
        self.errorMessage = error
    }
}
```

#### Step 3: Extend AXSettingsAttributes
```swift
extension AXSettingsAttributes {
    public var uiSection: UISectionValue? {
        get { self[UISection.self] }
        set { self[UISection.self] = newValue }
    }
    
    public var validationRule: ValidationRuleValue? {
        get { self[ValidationRule.self] }
        set { self[ValidationRule.self] = newValue }
    }
}
```

This is identical to how SwiftUI's `EnvironmentValues` work:
```swift
// SwiftUI pattern (for comparison)
private struct MyEnvironmentKey: EnvironmentKey {
    static let defaultValue: String = "default"
}

extension EnvironmentValues {
    var myValue: String {
        get { self[MyEnvironmentKey.self] }
        set { self[MyEnvironmentKey.self] = newValue }
    }
}
```

### Built-in Watch Key Example

Here's how the built-in `watchKey` attribute is implemented:

```swift
// MARK: Watch sync attribute
private struct AXWatchPreferenceKey: AXSettingsAttributeKey {
     static var defaultValue: AXPreferenceKeyPair? { nil }
}

public struct AXPreferenceKeyPair {
    public let key: String
    public let domain: String
    public init(key: String, domain: String = "com.apple.Accessibility") {
        self.key = key
        self.domain = domain
    }
}

extension AXSettingsAttributes {
     public var watchKey: AXPreferenceKeyPair? {
          get { self[AXWatchPreferenceKey.self] }
          set { self[AXWatchPreferenceKey.self] = newValue }
     }
}
```

### Setting Custom Attributes with @AXPrefAttr

Once you've defined custom attributes using the EnvironmentValues pattern, you set them using `@AXPrefAttr` with keypath syntax:

```swift
@AXSettingsGroup(domain: "com.myapp.settings")
class AppSettings {
    @AXPref(traits: [.watchSync])
    @AXPrefAttr(\.uiSection, UISectionValue(title: "Audio Settings", icon: "speaker.wave.3"))
    @AXPrefAttr(\.validationRule, ValidationRuleValue(min: 0.0, max: 1.0, error: "Volume must be between 0 and 1"))
    var masterVolume: Double = 0.5
    
    @AXPref(traits: [.guestPassInclude])
    @AXPrefAttr(\.uiSection, UISectionValue(title: "Privacy", icon: "lock.shield"))
    var allowGuestAccess: Bool = false
}
```

### Multiple Attributes on One Setting

You can apply multiple `@AXPrefAttr` macros to a single setting:

```swift
@AXPref(key: "advanced_logging", traits: [.buddyExclude])
@AXPrefAttr(\.uiSection, UISectionValue(title: "Developer", icon: "hammer"))
@AXPrefAttr(\.validationRule, ValidationRuleValue(min: 0.0, max: 10.0))
var debugLoggingLevel: Int = 0
```

### Real-World Example

Here's how the accessibility system uses custom attributes:

```swift
@AXSettingsGroup(domain: "com.apple.accessibility")
class VoiceOverSettings {
    @AXPref(traits: [.watchSync])
    @AXPrefAttr(\.watchKey, AXPreferenceKeyPair(key: "vo_speaking_rate"))
    var speakingRate: Double = 0.5
    
    @AXPref(key: "TripleClickChoice", traits: [.watchSync])
    @AXPrefAttr(\.watchKey, AXPreferenceKeyPair(key: "TripleClickOptions")) // Bridge sync uses different key
    var tripleClickItems: [AXSTripleClickOption] = []
    
    @AXPref(key: "NavigationStyle", traits: [.watchSync, .guestPassInclude])
    @AXPrefAttr(\.watchKey, AXPreferenceKeyPair(key: "nav_style_watch"))
    var navigationStyle: NavigationStyle = .grouped
}
```

### Accessing Attributes at Runtime

The attributes system provides runtime introspection capabilities. Thanks to dynamic member lookup, attributes are automatically passed through from the settings record, making access seamless:

```swift
let settings = AppSettings()

// Access the setting record to examine attributes
let volumeSetting = settings.$masterVolume // AXSettingRecord<Double>

// Check traits (special case attribute)  
if volumeSetting.traits.contains(.watchSync) {
    print("This setting syncs to watch")
}

// Access watch key mapping - dynamic member lookup makes this seamless
if let watchKey = volumeSetting.watchKey {
    print("Watch key: \(watchKey.key) in domain: \(watchKey.domain)")
}

// Access custom attributes directly through dynamic member lookup
if let uiSection = volumeSetting.uiSection {
    print("Section: \(uiSection.title), Icon: \(uiSection.icon)")
}

if let validation = volumeSetting.validationRule {
    print("Valid range: \(validation.minValue) - \(validation.maxValue)")
}
```

The dynamic member lookup system automatically forwards attribute access from the `AXSettingRecord` to the underlying `AXSettingsAttributes`, so you can access both built-in attributes like `watchKey` and your custom attributes directly on the setting record without needing to go through an `.attributes` property.

This makes the API clean and intuitive - just access the attribute name directly on the setting record, and the system handles the forwarding automatically.

### Why This Pattern?

This EnvironmentValues-inspired pattern provides several benefits:
- **Familiar to SwiftUI developers**: Uses the same mental model as SwiftUI's environment system
- **Type-safe**: Compile-time checking of attribute types and values
- **Extensible**: Easy to add new attributes without changing core system
- **Performant**: Efficient key-based lookup system
- **Composable**: Multiple attributes can be combined on a single setting

## 5. Runtime Querying & Introspection

### Enumerating Settings by Traits

The AXPref system provides powerful runtime introspection capabilities, allowing you to query and filter settings based on their traits and attributes. This is particularly useful for creating dynamic UIs, debugging tools, and cross-platform sync logic.

```swift
@AXSettingsGroup(domain: "com.myapp.settings")
class AppSettings {
    @AXPref(traits: [.watchSync])
    var notificationsEnabled: Bool = true
    
    @AXPref(traits: [.watchSync, .guestPassInclude])
    var themeMode: ThemeMode = .automatic
    
    @AXPref(traits: [.buddyExclude])
    var deviceSpecificSetting: String = "local-only"
    
    @AXPref(traits: [.managedAssets])
    var managedResourcePath: String = ""
}
```

### Querying Settings by Traits

```swift
let settings = AppSettings()

// Find all settings that sync to watch
let watchSyncedSettings = settings.settings(withTraits: [.watchSync])
for setting in watchSyncedSettings {
    print("Watch synced: \(setting.key) = \(setting.encodedValue ?? "nil")")
}

// Find settings that are guest accessible
let guestSettings = settings.settings(withTraits: [.guestPassInclude])

// Find settings with specific trait combinations
let watchAndGuestSettings = settings.settings(withTraits: [.watchSync, .guestPassInclude])

// Find settings that DON'T have certain traits
let localOnlySettings = settings.settings(excludingTraits: [.watchSync])
```

### Accessing Setting Metadata

Each setting record provides comprehensive metadata access:

```swift
let settings = AppSettings()

// Iterate through all settings in the group
for (propertyName, settingRecord) in settings.allSettings {
    let record = settingRecord as! AXSettingProtocol
    
    print("Setting: \(propertyName)")
    print("  Key: \(record.key)")
    print("  Domain: \(record.domain)")
    print("  Traits: \(record.traits)")
    print("  Current Value: \(record.encodedValue ?? "nil")")
    
    // Access custom attributes if present
    if let watchKey = record.watchKey {
        print("  Watch Key: \(watchKey.key) in \(watchKey.domain)")
    }
    
    print("---")
}
```

### Settings Groups and Hierarchical Organization

Settings groups can be organized hierarchically and queried at different levels:

```swift
@AXSettingsGroup(domain: "com.myapp.ui", keyPrefix: "Interface")
class UISettings {
    @AXPref(traits: [.watchSync])
    @AXPrefAttr(\.uiSection, UISectionValue(title: "Appearance", icon: "paintbrush"))
    var themeMode: ThemeMode = .automatic
    
    @AXPref(traits: [.guestPassInclude])
    @AXPrefAttr(\.uiSection, UISectionValue(title: "Accessibility", icon: "accessibility"))
    var highContrastMode: Bool = false
}

@AXSettingsGroup(domain: "com.myapp.data", keyPrefix: "Data")
class DataSettings {
    @AXPref(traits: [.buddyExclude])
    @AXPrefAttr(\.uiSection, UISectionValue(title: "Storage", icon: "internaldrive"))
    var cacheSize: Int = 100_000_000
}
```
When you nest settings groups, queries work accross all nested settings as well. 

### Dynamic UI Generation Based on Metadata

```swift
struct DynamicSettingsView: View {
    let settingsManager = AppSettingsManager()
    
    var body: some View {
        NavigationView {
            List {
                ForEach(settingsManager.settingsBySection().keys.sorted(), id: \.self) { sectionName in
                    Section(header: sectionHeader(for: sectionName)) {
                        ForEach(settingsManager.settingsBySection()[sectionName] ?? [], id: \.0) { name, setting in
                            settingRow(name: name, setting: setting)
                        }
                    }
                }
            }
            .navigationTitle("Settings")
        }
    }
    
    @ViewBuilder
    private func sectionHeader(for sectionName: String) -> some View {
        // Find the first setting in this section to get icon info
        if let firstSetting = settingsManager.settingsBySection()[sectionName]?.first?.1,
           let uiSection = firstSetting.uiSection {
            Label(sectionName, systemImage: uiSection.icon)
        } else {
            Text(sectionName)
        }
    }
    
    @ViewBuilder
    private func settingRow(name: String, setting: AXSettingProtocol) -> some View {
        HStack {
            Text(name.camelCaseToWords())
            Spacer()
            Text("\(setting.encodedValue?.description ?? "nil")")
                .foregroundColor(.secondary)
        }
        .badge(setting.traits.contains(.watchSync) ? "Watch" : nil)
    }
}

extension String {
    func camelCaseToWords() -> String {
        return self.replacingOccurrences(of: "([a-z])([A-Z])", 
                                       with: "$1 $2", 
                                       options: .regularExpression)
    }
}
```

## 6. Observability & Streaming

### Swift Concurrency Integration

The AXPref system is built from the ground up with Swift's modern concurrency features. Every setting automatically provides reactive streams and integrates seamlessly with Swift's `async`/`await` patterns and the Observation framework.

```swift
@AXSettingsGroup(domain: "com.myapp.settings")
class AppSettings {
    @AXPref(traits: [.watchSync])
    var notificationsEnabled: Bool = true
    
    @AXPref
    var userTheme: Theme = .automatic
    
    @AXPref
    var maxCacheSize: Int = 100_000_000
}
```

### AsyncStream Support for Real-time Updates

Every setting provides an `AsyncStream` that emits values whenever the setting changes:

```swift
let settings = AppSettings()

// Listen to a single setting
Task {
    for await enabled in settings.$notificationsEnabled.stream {
        print("Notifications enabled: \(enabled)")
        updateNotificationSystem(enabled: enabled)
    }
}

// Listen to multiple settings
Task {
    for await theme in settings.$userTheme.stream {
        print("Theme changed to: \(theme)")
        updateAppearance(theme: theme)
    }
}
```

### AXFetchableStream for Current + Future Values

The `fetchableStream` provides both the current value and future updates, perfect for UI initialization and reactive programming patterns:

```swift
let settings = AppSettings()

// Get current value immediately, then listen for changes
Task {
    let stream = settings.$notificationsEnabled.fetchableStream
    
    // Get current value by calling the stream as an async function
    let currentValue = await stream() // Async call
    updateUI(enabled: currentValue)
    
    // Listen for future changes
    for await newValue in stream {
        updateUI(enabled: newValue)
    }
}
```

### Advanced AXFetchableStream Usage

`AXFetchableStream` provides powerful transformation and composition methods for complex reactive scenarios:

```swift
// Transform values using asyncMap
let voiceSelectionStream: AXFetchableStream<[Locale.LanguageCode: VoiceSelection]> = settings.$voiceSelections.fetchableStream

// Create a derived stream for a specific language
let englishVoiceStream = voiceSelectionStream.asyncMap { selections in
    await selections.selection(forLanguage: .init("en"), withResolver: VoiceResolver.shared)
}

// Use the derived stream - async callable function for current value
Task {
    let currentEnglishVoice = await englishVoiceStream() // Async function call
    print("Current English voice: \(currentEnglishVoice?.voiceId ?? "none")")
    
    for await voice in englishVoiceStream {
        print("English voice changed: \(voice?.voiceId ?? "none")")
    }
}
```

### SwiftUI Integration

Settings work seamlessly with SwiftUI's observation system:

```swift
struct SettingsView: View {
    @Bindable private var settings = AppSettings()
    
    var body: some View {
        Form {
            Section("Notifications") {
                Toggle("Enable Notifications", isOn: $settings.notificationsEnabled)
            }
            
            Section("Appearance") {
                Picker("Theme", selection: settings.$userTheme) {
                    Text("Light").tag(Theme.light)
                    Text("Dark").tag(Theme.dark)
                    Text("Automatic").tag(Theme.automatic)
                }
            }
            
            Section("Storage") {
                Stepper("Max Cache Size: \(settings.maxCacheSize.formatted(.byteCount(style: .memory)))",
                       value: settings.$maxCacheSize,
                       in: 10_000_000...1_000_000_000,
                       step: 10_000_000)
            }
        }
        .navigationTitle("Settings")
    }
}
```


### Caching and Performance Considerations

The system includes intelligent caching to optimize performance:

```swift
let settings = AppSettings()

// First access reads from store and caches
let theme1 = settings.userTheme // Store access + cache

// Subsequent accesses use cache
let theme2 = settings.userTheme // Cache hit
let theme3 = settings.userTheme // Cache hit

// Cache is invalidated when value changes
settings.userTheme = .dark // Cache cleared, observers notified

// Next access reads fresh value
let theme4 = settings.userTheme // Store access + new cache
```

### Manual Cache Management

For advanced scenarios, you can manually control caching:

```swift
let settings = AppSettings()

// Purge cache for a specific setting
settings.$userTheme.purgeCache()

// Next access will read fresh from store
let freshTheme = settings.userTheme
```

## 6. Objective-C Interop

### @objc Declaration Exposure

The AXPref system provides seamless interoperability with Objective-C code through standard `@objc` declarations. Settings are exposed to Objective-C by adding `@objc` after the `@AXPref` macro.

```swift
@AXSettingsGroup(domain: "com.apple.accessibility")
class AccessibilitySettings {
    @AXPref(traits: [.watchSync]) @objc
    var voiceOverEnabled: Bool = false
    
    @AXPref @objc
    var speakingRate: Double = 0.5
    
    @AXPref // Not exposed to Objective-C
    var internalSwiftOnlySetting: String = "swift-only"
    
    @AXPref @objc
    var automaticallyExposed: Bool = true
}
```

### Objective-C Usage

Settings marked with `@objc` are automatically available in Objective-C:

```objc
AccessibilitySettings *settings = [[AccessibilitySettings alloc] init];

// Read values
BOOL voiceOverOn = settings.voiceOverEnabled;
double rate = settings.speakingRate;

// Write values
settings.voiceOverEnabled = YES;
settings.speakingRate = 0.75;

NSLog(@"VoiceOver: %@, Rate: %f", voiceOverOn ? @"ON" : @"OFF", rate);
```

### Objective-C Bridgeable Types

For Objective-C interop, you can only use types that are bridgeable to Objective-C. Complex Swift-only types cannot be directly exposed:

```swift
@AXSettingsGroup(domain: "com.apple.accessibility")
class VoiceSettings {
    // ✅ Objective-C bridgeable types work
    @AXPref @objc
    var voiceIdentifier: String = "com.apple.voice.compact.en-US.Samantha"
    
    @AXPref @objc  
    var speakingRate: Double = 0.5
    
    @AXPref @objc
    var isEnabled: Bool = true
    
    @AXPref @objc
    var voiceGender: Int = 0 // Use enum raw values
    
    @AXPref @objc
    var voiceSettings: NSDictionary = [
        "rate": 0.5,
        "pitch": 0.0,
        "volume": 1.0
    ]
    
    // ❌ Complex Swift types cannot be directly exposed with @objc
    @AXPref // No @objc - Swift only
    var complexSwiftStruct: VoicePreference = VoicePreference.default
    
    @AXPref // No @objc - Swift only  
    var advancedConfiguration: [String: CustomStruct] = [:]
}
```

### Legacy Integration with AXSettings_Swift.h

For existing Objective-C codebases that need to integrate with the new Swift settings system, you can expose Swift settings through the `AXSettings_Swift.h` header. This approach allows legacy Objective-C code to access Swift settings without requiring changes to the Objective-C implementation.

#### Adding to AXSettings_Swift.h

The `AXSettings_Swift.h` header acts as a bridge between your Swift settings and legacy Objective-C code. You add property declarations that forward to your Swift settings there. Objective c forwarding and reflection are used to forward the selector to the correct setting group. 

