How Settings Are Managed and Persisted in vorssaint-utils: A Complete Technical Guide

vorssaint-utils persists all user preferences using macOS UserDefaults, accessed through type-safe keys defined in a central DefaultsKey enum and sanitized via a helper Defaults struct.

The vorssaint-utils macOS application implements a robust persistence layer for user-configurable preferences. By wrapping Apple's UserDefaults API with a consistent key-naming strategy and data sanitization utilities, the codebase ensures that settings remain consistent across sessions while remaining easy to maintain and test.

The UserDefaults Architecture in vorssaint-utils

The architecture isolates raw storage behind well-named keys and helper methods, ensuring that every component reads and writes preferences in a consistent, testable way. All settings are persisted automatically by the OS and restored on next launch, providing a seamless user experience.

Type-Safe Key Definitions with DefaultsKey

At the core of the system lies a central enum or struct named DefaultsKey that defines string constants for every preference option. This approach keeps the key space consistent and prevents string-typo bugs across the codebase.

// Example key definition
DefaultsKey.windowPreviewExcludedApps
DefaultsKey.windowGestureEnabled
DefaultsKey.radialMenuProfiles

Standard vs. Suite-Based Storage

Most components access preferences through UserDefaults.standard, the shared defaults object for the current user. However, the codebase also supports named suites via UserDefaults(suiteName: ...) for specific migration scenarios or sandboxed components, such as when interacting with com.apple.WindowManager.

The Settings Persistence Workflow

The repository follows a predictable five-step workflow for managing user preferences, from definition to UI observation.

Defining and Reading Preferences

UI components and services read values using the standard UserDefaults API, often providing fallback defaults when keys are missing. In Sources/Vorssaint/UI/Settings/WindowPreviewExclusionsList.swift, the application retrieves excluded applications using:

// Read a boolean preference
let gesturesEnabled = UserDefaults.standard.bool(forKey: DefaultsKey.windowGestureEnabled)

// Read an array with fallback to empty list
let excludedApps = UserDefaults.standard.stringArray(forKey: DefaultsKey.windowPreviewExcludedApps) ?? []

Data Sanitization

Before persisting or using raw UserDefaults data, the Defaults helper struct centralizes common logic to clean values. For bundle identifiers, Defaults.sanitizedBundleIdentifierList(...) removes empty strings and invalid entries, ensuring downstream components work with clean data:

let sanitized = Defaults.sanitizedBundleIdentifierList(bundleIDs)

Writing Values Back to Storage

Updates flow back through the standard API. In Sources/Vorssaint/UI/Settings/ShortcutsSettings.swift, sanitized data is persisted using:

func saveExcludedApps(_ bundleIDs: [String]) {
    let sanitized = Defaults.sanitizedBundleIdentifierList(bundleIDs)
    UserDefaults.standard.set(sanitized, forKey: DefaultsKey.windowPreviewExcludedApps)
}

Observing Changes in SwiftUI

Views refresh automatically using @State or @ObservedObject wrappers that initialize from saved values. When the underlying UserDefaults value changes, the UI updates to reflect the new state without requiring manual notification handling.

Handling Different Data Types

The implementation handles primitives, collections, and complex objects with specific patterns for each category.

Boolean Toggles

Simple on/off preferences use the native boolean API. The window-gesture service throughout the codebase checks flags using:

let enabled = UserDefaults.standard.bool(forKey: DefaultsKey.windowGestureEnabled)

Values are written using set(_:forKey:) with the appropriate DefaultsKey constant.

Collections and Property Lists

Array and dictionary values store as property-list-compatible collections. The window preview exclusion list manages arrays of bundle identifier strings:

// Retrieval
let apps = UserDefaults.standard.stringArray(forKey: DefaultsKey.windowPreviewExcludedApps) ?? []

// Persistence
UserDefaults.standard.set(sanitizedArray, forKey: DefaultsKey.windowPreviewExcludedApps)

Complex Codable Objects

For structured data like radial-menu profiles, the application encodes objects to Data using Codable helpers before storage. In Sources/Vorssaint/UI/Settings/RadialMenuSettings.swift:

// Encoding and saving
let encoded = RadialMenuSupport.encodeProfiles(profiles)
UserDefaults.standard.set(encoded, forKey: DefaultsKey.radialMenuProfiles)

// Retrieval and decoding
if let data = UserDefaults.standard.data(forKey: DefaultsKey.radialMenuProfiles) {
    let profiles = RadialMenuSupport.decodeProfiles(data)
}

Key Implementation Files

Several source files demonstrate the unified persistence pattern across different subsystems:

  • Sources/Vorssaint/UI/Settings/WindowPreviewExclusionsList.swift – Manages the window-preview exclusion list UI, reading and writing bundle ID arrays via UserDefaults.standard.

  • Sources/Vorssaint/UI/Settings/ShortcutsSettings.swift – Handles shortcut-related toggles, implementing read/write cycles for keyboard preference changes.

  • Sources/Vorssaint/UI/Settings/RadialMenuSettings.swift – Persists radial-menu profiles as encoded Data objects using RadialMenuSupport encoding helpers.

  • Sources/Vorssaint/Services/Update/UpdateService.swift – Stores auto-check and beta-update flags as boolean values in standard defaults.

  • Sources/Vorssaint/Services/WindowLayout/WindowLayoutSupport.swift – Reads layout spacing values and shortcut strings from persistent storage on initialization.

  • Sources/Vorssaint/Support/Uninstaller.swift – Checks a persistent flag via UserDefaults to determine whether to skip system sleep during cleanup operations.

Summary

  • vorssaint-utils uses macOS UserDefaults as the underlying persistence mechanism for all user preferences.
  • A central DefaultsKey enum provides type-safe string constants, preventing key-naming errors across the codebase.
  • The Defaults helper struct encapsulates sanitization logic, ensuring that raw UserDefaults data is cleaned before use.
  • Complex objects are persisted by encoding them to Data via Codable protocols, while primitives and arrays use native UserDefaults methods.
  • The architecture supports both standard defaults and named suites for migration or sandboxing scenarios.
  • Implementation files in Sources/Vorssaint/UI/Settings/ and Sources/Vorssaint/Services/ demonstrate consistent read-write-observe patterns throughout the application.

Frequently Asked Questions

Where does vorssaint-utils store user preferences?

vorssaint-utils stores all user-configurable preferences in the macOS UserDefaults system. Most settings use UserDefaults.standard, while specific components may use named suites via UserDefaults(suiteName: ...) for sandboxing or migration purposes. The OS handles persistence automatically, requiring no manual file management.

How does the app ensure type safety when accessing UserDefaults?

The application defines a central enum or struct called DefaultsKey that exports string constants for every preference. By requiring developers to use these constants instead of raw strings, the codebase prevents typos and provides compile-time checking. All access flows through these keys, ensuring consistent reads and writes across Sources/Vorssaint/UI/Settings/ and service layers.

Can vorssaint-utils settings be migrated between different UserDefaults suites?

Yes. While the majority of the codebase uses UserDefaults.standard, specific components utilize suite-based initialization with UserDefaults(suiteName: "com.apple.WindowManager") for migration or sandboxed storage scenarios. This allows the application to interact with system-level preferences or transition data between different containers when necessary.

How are complex objects like radial menu profiles persisted?

Complex objects are encoded to Data using Codable protocol implementations before storage. For example, RadialMenuSupport.encodeProfiles(profiles) converts profile objects to data that UserDefaults.standard.set(_:forKey:) can store. Retrieval reverses this process: fetching the Data value and decoding it through RadialMenuSupport.decodeProfiles(data). This pattern appears in RadialMenuSettings.swift and other settings files handling non-primitive types.

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 →