How to Access Application Settings in FluidVoice: Complete Guide to SettingsStore

FluidVoice stores all user-configurable data in a thread-safe singleton called SettingsStore that conforms to ObservableObject, allowing SwiftUI views to subscribe to changes via @ObservedObject while automatically persisting values to UserDefaults.

The FluidVoice repository organizes application preferences through a centralized architecture defined in Sources/Fluid/Persistence/SettingsStore.swift. This approach ensures that whether you are building a preference pane, reacting to hotkey changes, or configuring dictation prompts, you access application settings in FluidVoice through a single, consistent API backed by UserDefaults and KeychainService for sensitive data.

What is SettingsStore?

SettingsStore is the central nervous system for FluidVoice configuration. Implemented as a singleton accessible via SettingsStore.shared, this class conforms to ObservableObject and manages the persistence layer for all user preferences.

The store automatically handles serialization through UserDefaults (with KeychainService handling secrets), eliminates manual save calls, and broadcasts changes via objectWillChange.send() to trigger SwiftUI view updates. Located in Sources/Fluid/Persistence/SettingsStore.swift, it exposes typed properties and helper methods that provide type-safe access to raw preference values.

How to Access Application Settings in FluidVoice

Reading Settings

Access any setting by querying the computed property on the shared instance. The store provides typed accessors that abstract away the underlying UserDefaults keys.

import Fluid

// Access the transcription preview character limit
let limit = SettingsStore.shared.transcriptionPreviewCharLimit

// Retrieve the active dictation prompt for a specific slot
let selection = SettingsStore.shared.dictationPromptSelection(for: .primary)

// Check if launch at startup is enabled
let autoLaunch = SettingsStore.shared.launchAtStartupEnabled

Writing Settings

Assign values directly to properties; the store automatically persists changes and notifies observers. This pattern appears throughout Sources/Fluid/Views/CommandModeView.swift and other view files.

// Update the preview limit with bounds checking
let current = SettingsStore.shared.transcriptionPreviewCharLimit
let step = SettingsStore.transcriptionPreviewCharLimitStep
let max = SettingsStore.transcriptionPreviewCharLimitRange.upperBound
SettingsStore.shared.transcriptionPreviewCharLimit = min(current + step, max)

// Configure a hotkey for a specific prompt selection
var config = SettingsStore.shared.dictationPromptConfiguration(for: selection)
config.shortcut = newHotkey
SettingsStore.shared.setDictationPromptConfiguration(config, for: selection)

Observing Changes

Inject the store into SwiftUI views using @ObservedObject to ensure the UI reacts to preference mutations. This pattern is demonstrated in Sources/Fluid/fluidApp.swift where the top-level view maintains a reference to SettingsStore.shared.

import SwiftUI

struct PreviewLimitView: View {
    @ObservedObject private var settings = SettingsStore.shared

    var body: some View {
        Text("Preview limit: \(settings.transcriptionPreviewCharLimit) characters")
            .padding()
    }
}

Common Settings and Properties

The following settings represent the most frequently accessed configuration options within the FluidVoice codebase:

  • transcriptionPreviewCharLimit – Controls the character limit for the transcription preview pane. Accessed in Sources/Fluid/Views/NotchContentViews.swift.

  • dictationPromptSelection(for:) – Returns the active prompt selection (default, profile, Private AI, or off) for a given shortcut slot. Used extensively in Sources/Fluid/Views/CommandModeView.swift.

  • selectedDictationPromptProfile – Identifies the currently chosen dictation prompt profile, if any.

  • defaultPromptOverride(for:) – Retrieves optional user-provided overrides for built-in system prompts (e.g., .dictate mode).

  • launchAtStartupEnabled – Boolean indicating whether FluidVoice registers as a macOS login item.

  • availableModelsByProvider – Dictionary of AI models organized by provider, accessed in Sources/Fluid/Views/RewriteModeView.swift to populate model selection interfaces.

Practical Code Examples

Observing Settings from a Service

For non-SwiftUI contexts such as background services, subscribe to the objectWillChange publisher to react to configuration updates.

import Combine

class PromptService {
    private var cancellable: AnyCancellable?

    init() {
        cancellable = SettingsStore.shared.objectWillChange.sink { _ in
            self.reloadPrompt()
        }
    }

    private func reloadPrompt() {
        let prompt = SettingsStore.shared.effectiveSystemPrompt(for: .dictate)
        // Pass updated prompt to AI provider...
    }
}

Determining Active Prompt Description

Handle the DictationPromptSelection enum to provide user-facing descriptions of the current configuration.

func activePromptDescription() -> String {
    let selection = SettingsStore.shared.dictationPromptSelection(for: .primary)
    switch selection {
    case .off:
        return "Dictation is turned off"
    case .default:
        return "Using the default system prompt"
    case .privateAI:
        return "Using Private AI prompt"
    case .profile(let id):
        return "Using custom profile \(id)"
    }
}

Accessing Visualizer Settings

The NotchContentViews.swift file demonstrates accessing audio visualization thresholds.

// From Sources/Fluid/Views/NotchContentViews.swift
let noiseThreshold = SettingsStore.shared.visualizerNoiseThreshold

Summary

  • FluidVoice centralizes all application settings in SettingsStore, a thread-safe singleton defined in Sources/Fluid/Persistence/SettingsStore.swift.
  • Access settings through SettingsStore.shared using typed properties that abstract UserDefaults persistence.
  • Observe changes by injecting the store as an @ObservedObject in SwiftUI views or subscribing to objectWillChange in Combine-based services.
  • Modify values by direct assignment; the store automatically triggers persistence and UI updates without manual save calls.
  • Secure sensitive data through internal KeychainService integration while standard preferences use UserDefaults.

Frequently Asked Questions

Where are FluidVoice settings physically stored?

Settings are persisted to UserDefaults standard storage, with sensitive credentials managed by the internal KeychainService class. You never need to call synchronize() or perform manual writes; the SettingsStore handles persistence automatically when you modify any property.

How do I observe settings changes in SwiftUI?

Inject SettingsStore.shared as an @ObservedObject in your view struct. Because the store conforms to ObservableObject and calls objectWillChange.send() on every mutation, any view containing @ObservedObject var settings = SettingsStore.shared will automatically redraw when settings change, as implemented in Sources/Fluid/fluidApp.swift.

Is SettingsStore thread-safe?

Yes. The singleton instance is designed to be accessed from any thread or queue safely. While SwiftUI mutations must occur on the main thread by architectural requirement, the underlying UserDefaults and KeychainService operations are thread-safe, allowing background services to read settings without race conditions.

How do I access settings from a non-SwiftUI context?

Use the Combine framework to subscribe to SettingsStore.shared.objectWillChange. This publisher emits whenever any setting changes, allowing services, view models, or background tasks to react to configuration updates without maintaining a direct view reference, as shown in the PromptService example above.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →