Files

10 KiB

source, confidence, namespace, last_synced, alwaysApply
source confidence namespace last_synced alwaysApply
~/DuckDuckGo/apple-browsers.git/main/.cursor/rules/feature-flags.mdc 0.9 work 2026-04-28 false

Feature Flag Patterns

Type-Safe Feature Flags

Use enum-based feature flags with protocols for type safety:

// ✅ CORRECT - Type-safe feature flags
protocol FeatureFlag: RawRepresentable where RawValue == String {
    var defaultValue: Bool { get }
    var description: String { get }
}

enum UIFeatureFlag: String, FeatureFlag {
    case newTabPageRedesign = "new_tab_page_redesign"
    case advancedPrivacySettings = "advanced_privacy_settings"
    case voiceSearch = "voice_search"
    case experimentalUI = "experimental_ui"
    
    var defaultValue: Bool {
        switch self {
        case .newTabPageRedesign: return false
        case .advancedPrivacySettings: return true
        case .voiceSearch: return false
        case .experimentalUI: return false
        }
    }
    
    var description: String {
        switch self {
        case .newTabPageRedesign: 
            return "Enable redesigned new tab page"
        case .advancedPrivacySettings: 
            return "Show advanced privacy settings"
        case .voiceSearch: 
            return "Enable voice search functionality"
        case .experimentalUI:
            return "Enable experimental UI components"
        }
    }
}

enum NetworkFeatureFlag: String, FeatureFlag {
    case networkProtectionV2 = "network_protection_v2"
    case enhancedBlocking = "enhanced_blocking"
    
    var defaultValue: Bool {
        switch self {
        case .networkProtectionV2: return false
        case .enhancedBlocking: return true
        }
    }
    
    var description: String {
        switch self {
        case .networkProtectionV2:
            return "Enable Network Protection V2"
        case .enhancedBlocking:
            return "Enhanced content blocking"
        }
    }
}

FeatureFlagger Protocol Implementation

Extend the existing FeatureFlagger with type-safe methods:

// ✅ CORRECT - Type-safe FeatureFlagger extension
extension FeatureFlagger {
    func isEnabled<Flag: FeatureFlag>(_ flag: Flag) -> Bool {
        return isFeatureOn(flag.rawValue) ?? flag.defaultValue
    }
    
    func setEnabled<Flag: FeatureFlag>(_ enabled: Bool, for flag: Flag) {
        setFeatureOn(flag.rawValue, enabled: enabled)
    }
}

// Usage in code
final class FeatureViewModel: ObservableObject {
    private let featureFlagger: FeatureFlagger
    
    init(featureFlagger: FeatureFlagger) {
        self.featureFlagger = featureFlagger
    }
    
    func loadContent() {
        if featureFlagger.isEnabled(UIFeatureFlag.newTabPageRedesign) {
            loadNewDesign()
        } else {
            loadLegacyDesign()
        }
    }
}

Feature Flag ViewModifier

Create SwiftUI modifiers for conditional UI:

// ✅ ADVANCED - SwiftUI feature flag modifier
struct FeatureFlagModifier<Flag: FeatureFlag>: ViewModifier {
    let flag: Flag
    let featureFlagger: FeatureFlagger
    let fallback: () -> AnyView
    
    func body(content: Content) -> some View {
        if featureFlagger.isEnabled(flag) {
            content
        } else {
            fallback()
        }
    }
}

extension View {
    func featureFlag<Flag: FeatureFlag>(
        _ flag: Flag,
        featureFlagger: FeatureFlagger,
        @ViewBuilder fallback: @escaping () -> some View = { EmptyView() }
    ) -> some View {
        modifier(FeatureFlagModifier(
            flag: flag,
            featureFlagger: featureFlagger,
            fallback: { AnyView(fallback()) }
        ))
    }
}

// Usage
struct ContentView: View {
    @Environment(\.dependencies) var dependencies
    
    var body: some View {
        VStack {
            NewFeatureView()
                .featureFlag(UIFeatureFlag.experimentalUI, 
                           featureFlagger: dependencies.featureFlagger) {
                    LegacyFeatureView()
                }
        }
    }
}

Feature Flag Property Wrapper

Create a property wrapper for reactive feature flags:

// ✅ ADVANCED - Reactive feature flag property wrapper
@propertyWrapper
struct FeatureFlagState<Flag: FeatureFlag>: DynamicProperty {
    @ObservedObject private var flagger: ObservableFeatureFlagger
    private let flag: Flag
    
    var wrappedValue: Bool {
        get { flagger.isEnabled(flag) }
        nonmutating set { flagger.setEnabled(newValue, for: flag) }
    }
    
    var projectedValue: Binding<Bool> {
        Binding(
            get: { wrappedValue },
            set: { wrappedValue = $0 }
        )
    }
    
    init(_ flag: Flag) {
        self.flag = flag
        self._flagger = ObservedObject(wrappedValue: ObservableFeatureFlagger.shared)
    }
}

// Usage in SwiftUI views
struct SettingsView: View {
    @FeatureFlagState(UIFeatureFlag.voiceSearch) var voiceSearchEnabled
    @FeatureFlagState(UIFeatureFlag.experimentalUI) var experimentalUIEnabled
    
    var body: some View {
        Form {
            Toggle("Voice Search", isOn: $voiceSearchEnabled)
            Toggle("Experimental UI", isOn: $experimentalUIEnabled)
        }
    }
}

A/B Testing Integration

Integrate feature flags with A/B testing:

// ✅ ADVANCED - A/B testing with feature flags
enum ABTestVariant: String, CaseIterable {
    case control = "control"
    case variantA = "variant_a"
    case variantB = "variant_b"
    
    var displayName: String {
        switch self {
        case .control: return "Control Group"
        case .variantA: return "Variant A"
        case .variantB: return "Variant B"
        }
    }
}

protocol ABTestFeatureFlag: FeatureFlag {
    var variants: [ABTestVariant] { get }
    var currentVariant: ABTestVariant { get }
}

enum ExperimentalFeatureFlag: String, ABTestFeatureFlag {
    case newOnboardingFlow = "new_onboarding_flow"
    case redesignedSearch = "redesigned_search"
    
    var defaultValue: Bool { true }
    
    var description: String {
        switch self {
        case .newOnboardingFlow: return "New onboarding flow experiment"
        case .redesignedSearch: return "Redesigned search interface experiment"
        }
    }
    
    var variants: [ABTestVariant] {
        [.control, .variantA, .variantB]
    }
    
    var currentVariant: ABTestVariant {
        // Get variant from A/B testing service
        return ABTestingService.shared.getVariant(for: self.rawValue)
    }
}

// Usage with variants
func configureOnboarding() {
    let experiment = ExperimentalFeatureFlag.newOnboardingFlow
    
    switch experiment.currentVariant {
    case .control:
        showLegacyOnboarding()
    case .variantA:
        showNewOnboardingVariantA()
    case .variantB:
        showNewOnboardingVariantB()
    }
    
    // Track experiment exposure
    PixelFiring.fire(.experimentExposure(experiment.rawValue, experiment.currentVariant.rawValue))
}

Remote Feature Flags

Integrate with remote configuration:

// ✅ ADVANCED - Remote feature flag management
protocol RemoteFeatureFlag: FeatureFlag {
    var remoteKey: String { get }
    var localOverrideKey: String? { get }
}

extension RemoteFeatureFlag {
    var remoteKey: String { rawValue }
    var localOverrideKey: String? { "local_override_\(rawValue)" }
}

enum RemoteUIFeatureFlag: String, RemoteFeatureFlag {
    case serverDrivenUI = "server_driven_ui"
    case dynamicThemes = "dynamic_themes"
    
    var defaultValue: Bool { false }
    
    var description: String {
        switch self {
        case .serverDrivenUI: return "Server-driven UI configuration"
        case .dynamicThemes: return "Dynamic theme system"
        }
    }
}

final class RemoteFeatureFlagger: FeatureFlagger {
    private let remoteConfig: RemoteConfigProtocol
    private let localDefaults: UserDefaults
    
    init(remoteConfig: RemoteConfigProtocol, localDefaults: UserDefaults) {
        self.remoteConfig = remoteConfig
        self.localDefaults = localDefaults
    }
    
    func isEnabled<Flag: RemoteFeatureFlag>(_ flag: Flag) -> Bool {
        // Check local override first
        if let overrideKey = flag.localOverrideKey,
           let localOverride = localDefaults.object(forKey: overrideKey) as? Bool {
            return localOverride
        }
        
        // Check remote config
        if let remoteValue = remoteConfig.boolValue(for: flag.remoteKey) {
            return remoteValue
        }
        
        // Fall back to default
        return flag.defaultValue
    }
    
    func setLocalOverride<Flag: RemoteFeatureFlag>(_ enabled: Bool?, for flag: Flag) {
        guard let overrideKey = flag.localOverrideKey else { return }
        
        if let enabled = enabled {
            localDefaults.set(enabled, forKey: overrideKey)
        } else {
            localDefaults.removeObject(forKey: overrideKey)
        }
    }
}

Debug Feature Flag Interface

Create debug interface for testing:

// ✅ DEBUG - Feature flag debug interface
#if DEBUG
struct FeatureFlagDebugView: View {
    @StateObject private var debugFlags = DebugFeatureFlags()
    
    var body: some View {
        NavigationView {
            List {
                Section("UI Features") {
                    ForEach(UIFeatureFlag.allCases, id: \.rawValue) { flag in
                        FeatureFlagRow(flag: flag, debugFlags: debugFlags)
                    }
                }
                
                Section("Network Features") {
                    ForEach(NetworkFeatureFlag.allCases, id: \.rawValue) { flag in
                        FeatureFlagRow(flag: flag, debugFlags: debugFlags)
                    }
                }
            }
            .navigationTitle("Feature Flags")
        }
    }
}

struct FeatureFlagRow<Flag: FeatureFlag>: View where Flag: CaseIterable {
    let flag: Flag
    @ObservedObject var debugFlags: DebugFeatureFlags
    
    var body: some View {
        Toggle(isOn: Binding(
            get: { debugFlags.isEnabled(flag) },
            set: { debugFlags.setEnabled($0, for: flag) }
        )) {
            VStack(alignment: .leading) {
                Text(flag.rawValue)
                    .font(.headline)
                Text(flag.description)
                    .font(.caption)
                    .foregroundColor(.secondary)
            }
        }
    }
}
#endif

See analytics-patterns.md for pixel firing patterns and configuration-management.md for advanced configuration management.