Skip to main content
Nook uses specialized managers for each feature domain. Each manager owns the business logic, state, and coordination for its area of responsibility.

Manager pattern overview

Managers in Nook follow these principles:

Single responsibility

Each manager handles one feature domain (tabs, profiles, extensions, etc.)

Observable state

Managers expose state via @Observable or @Published for reactive UI updates

MainActor confinement

All managers are @MainActor for thread-safe UI interaction

Environment injection

Managers are injected via SwiftUI environment, not global singletons (goal)

Manager directory

Nook has 27 managers in Nook/Managers/:

Core managers

Location: Nook/Managers/BrowserManager/BrowserManager.swift:18Status: Legacy “god object” (~2800 lines) being refactoredResponsibilities:
  • Central coordinator that connects all other managers
  • Owns TabManager, ProfileManager, ExtensionManager, etc.
  • Routes commands from menu bar and keyboard shortcuts
  • Handles window lifecycle and state synchronization
Pattern: ObservableObject with @Published properties (Combine)Refactoring plan: Eliminate by making managers independent and environment-injected
@MainActor
final class BrowserManager: ObservableObject {
    let tabManager: TabManager
    let profileManager: ProfileManager
    let extensionManager: ExtensionManager
    // ... ~30 manager properties
}
Location: Nook/Managers/TabManager/TabManager.swiftResponsibilities:
  • Tab lifecycle (create, close, reorder, pin)
  • Space organization and active tab tracking
  • Folder management within spaces
  • Atomic persistence via PersistenceActor
  • Session restoration from SwiftData
Key features:
  • Coalesced writes to prevent excessive I/O
  • Backup/recovery on persistence failure
  • Global pinned tabs (essentials) vs space-pinned tabs
// TabManager.swift:12
actor PersistenceActor {
    func persist(snapshot: Snapshot, generation: Int) async -> Bool
}
Pattern: Uses Swift actor for thread-safe persistence
Location: Nook/Managers/ProfileManager/ProfileManager.swift:13Responsibilities:
  • Manage persistent profiles (each with isolated WKWebsiteDataStore)
  • Create/delete/rename profiles
  • Handle ephemeral/incognito profiles (non-persistent data stores)
  • Profile switching and data isolation
Key features:
  • Each profile gets a unique WKWebsiteDataStore(forIdentifier: profileId)
  • Ephemeral profiles use WKWebsiteDataStore.nonPersistent()
  • Prevents deletion of last profile
// ProfileManager.swift:18-19
@Published var profiles: [Profile] = []
private var ephemeralProfiles: [UUID: Profile] = [:] // windowId -> profile
Location: Nook/Managers/WindowRegistry/WindowRegistry.swift:14Responsibilities:
  • Track all open browser windows
  • Maintain active (focused) window state
  • Coordinate window lifecycle (register, unregister, activate)
  • Provide callbacks for window events
Pattern: @Observable with callbacks for lifecycle events
@MainActor
@Observable
class WindowRegistry {
    var windows: [UUID: BrowserWindowState] = [:]
    var activeWindowId: UUID?
    
    var onWindowClose: ((UUID) -> Void)?
    var onWindowRegister: ((BrowserWindowState) -> Void)?
    var onActiveWindowChange: ((BrowserWindowState) -> Void)?
}
Location: Nook/Managers/WebViewCoordinator/WebViewCoordinator.swift:14Responsibilities:
  • Manage webview instances across multiple windows
  • Coordinate primary vs clone webviews for multi-window tabs
  • Prevent duplicate webview creation
  • Clean up webviews when windows close
Key algorithm (WebViewCoordinator.swift:96-131):
  1. If tab not shown in any window → create primary webview
  2. If tab already shown elsewhere → create clone webview
  3. If window already has webview for tab → return existing
// WebViewCoordinator.swift:16
private var webViewsByTabAndWindow: [UUID: [UUID: WKWebView]] = [:]

Feature managers

Location: Nook/Managers/ExtensionManager/ExtensionManager.swiftResponsibilities:
  • WKWebExtensionController integration (macOS 15.4+)
  • Extension installation, loading, updating
  • Permission management (install-time and runtime)
  • Native messaging host integration
  • Extension bridge for chrome.* APIs
Pattern: Singleton (required for WebKit integration)Complexity: ~3800 lines, most complex managerSee Extension system docs for details.
Location: Nook/Managers/HistoryManager/HistoryManager.swiftResponsibilities:
  • Track browsing history per profile
  • Persist history entries to SwiftData (HistoryEntity)
  • Search history by title/URL
  • Clear history with date filters
Location: Nook/Managers/DownloadManager/DownloadManager.swiftResponsibilities:
  • Handle WKDownloadDelegate callbacks
  • Track active downloads
  • Provide download progress UI
  • Manage download destination paths
Location: Nook/Managers/CookieManager/CookieManager.swiftResponsibilities:
  • Cookie inspection and management
  • Clear cookies per profile or globally
  • Export/import cookie data
Location: Nook/Managers/CacheManager/CacheManager.swiftResponsibilities:
  • Manage WKWebsiteDataStore cache
  • Clear cache per profile
  • Calculate cache size estimates
Location: Nook/Managers/SearchManager/SearchManager.swiftResponsibilities:
  • Handle search engine configuration
  • Parse search queries vs URLs in address bar
  • Provide autocomplete suggestions
Location: Nook/Managers/DialogManager/DialogManager.swiftResponsibilities:
  • Show system dialogs (alerts, confirmations)
  • Manage dialog queue and presentation
  • Handle basic auth prompts
Location: Nook/Managers/FindManager/FindManager.swiftResponsibilities:
  • In-page search (find bar)
  • Highlight matches
  • Navigate between results
Location: Nook/Managers/ZoomManager/ZoomManager.swiftResponsibilities:
  • Track zoom levels per tab
  • Apply zoom presets
  • Persist zoom preferences
Location: Nook/Managers/SplitViewManager/SplitViewManager.swiftResponsibilities:
  • Manage split-screen tab viewing
  • Coordinate primary/secondary pane state
  • Clean up split state on window close

UI managers

Location: Nook/Managers/GradientColorManager/GradientColorManager.swiftResponsibilities:
  • Manage space gradient themes
  • Generate gradient animations
  • Persist gradient configurations
Location: Nook/Managers/HoverSidebarManager/HoverSidebarManager.swiftResponsibilities:
  • Detect mouse hover at screen edge
  • Show/hide sidebar with animation
  • Track hover state per window
Location: Nook/Managers/PeekManager/PeekManager.swiftResponsibilities:
  • Show link preview overlays (Option + hover)
  • Manage peek webview lifecycle
  • Handle peek-to-tab conversion
Location: Nook/Managers/DragManager/TabDragManager.swiftResponsibilities:
  • Handle tab drag-and-drop
  • Reorder tabs within spaces
  • Move tabs between spaces and windows

Specialized managers

Location: Nook/Managers/AIManager/AIService.swiftResponsibilities:
  • AI chat integration (OpenAI, Gemini, Ollama, OpenRouter)
  • MCP (Model Context Protocol) server management
  • Browser tool execution for AI actions
Location: Nook/Managers/KeyboardShortcutManager/KeyboardShortcutManager.swiftResponsibilities:
  • Global keyboard shortcut registration
  • Command routing to managers
  • Conflict detection and warnings
Location: Nook/Managers/AuthenticationManager/AuthenticationManager.swiftResponsibilities:
  • HTTP basic auth credential storage
  • WebAuthn/Passkey integration
  • Credential prompt handling
Location: Nook/Managers/ImportManager/ImportManager.swiftResponsibilities:
  • Import bookmarks/history from Safari, Chrome, Firefox
  • Parse browser export formats
  • Merge imported data with existing tabs/spaces
Location: Nook/Managers/PrivacyManager/PrivacyManager.swiftResponsibilities:
  • Configure tracking protection
  • Manage content blockers
  • Handle privacy-related WebKit settings

BrowserManager as coordinator

BrowserManager currently acts as the glue between all managers:
// BrowserManager.swift (simplified)
@MainActor
final class BrowserManager: ObservableObject {
    // Core managers
    let tabManager: TabManager
    let profileManager: ProfileManager
    let extensionManager: ExtensionManager
    let historyManager: HistoryManager
    
    // UI managers
    let dialogManager: DialogManager
    let findManager: FindManager
    let zoomManager: ZoomManager
    let splitManager: SplitViewManager
    let gradientColorManager: GradientColorManager
    
    // Specialized managers
    let downloadManager: DownloadManager
    let cookieManager: CookieManager
    let cacheManager: CacheManager
    let searchManager: SearchManager
    
    // Coordinators (temporary)
    var webViewCoordinator: WebViewCoordinator?
    var windowRegistry: WindowRegistry?
    
    // Commands route through BrowserManager
    func createNewTab() {
        tabManager.createTab(...)
    }
    
    func switchProfile(_ profile: Profile) {
        profileManager.setActiveProfile(profile)
    }
}

Refactoring roadmap

The goal is to eliminate BrowserManager by making managers independent: Before (current):
@EnvironmentObject var browserManager: BrowserManager
browserManager.tabManager.createTab(...)
browserManager.profileManager.switchProfile(...)
After (target):
@Environment(TabManager.self) private var tabManager
@Environment(ProfileManager.self) private var profileManager

tabManager.createTab(...)
profileManager.switchProfile(...)
This requires:
  1. Migrating all managers to @Observable (Swift Observation)
  2. Removing cross-manager dependencies where possible
  3. Using protocols/callbacks for necessary coordination

Manager lifecycle

Initialization

Managers are created in NookApp.init() and BrowserManager.init():
// NookApp.swift:18-24
@State private var windowRegistry = WindowRegistry()
@State private var webViewCoordinator = WebViewCoordinator()
@StateObject private var browserManager = BrowserManager()

// BrowserManager.init() creates child managers
self.tabManager = TabManager(context: context)
self.profileManager = ProfileManager(context: context)

Environment injection

Managers flow down the view hierarchy:
// NookApp.swift:46-56
ContentView()
    .environmentObject(browserManager)           // Legacy Combine
    .environment(windowRegistry)                 // Swift Observation
    .environment(webViewCoordinator)             // Swift Observation
    .environment(\.nookSettings, settingsManager) // Environment value

Cleanup

Managers clean up resources in AppDelegate.applicationWillTerminate():
func applicationWillTerminate(_ notification: Notification) {
    browserManager?.cleanup()
    windowRegistry?.windows.values.forEach { windowState in
        windowRegistry?.onWindowClose?(windowState.id)
    }
}

Best practices

When working with managers:
Prefer environment injection over singletonsEven though some managers (like ExtensionManager) use singletons for WebKit compatibility, new code should use environment injection.
Use @MainActor for all managersSince managers interact with SwiftUI state, they must be @MainActor confined to avoid data races.
Avoid circular dependenciesIf Manager A needs Manager B, consider:
  • Using a protocol/delegate pattern
  • Injecting a callback closure
  • Emitting events via NotificationCenter

Next steps

Models

Explore data models used by managers

State management

Learn about @Observable vs @Published patterns

Build docs developers (and LLMs) love